mirror of
https://github.com/chylex/Nextcloud-Desktop.git
synced 2026-04-14 01:30:37 +02:00
Compare commits
223 Commits
v3.3.0-rc1
...
v3.3.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d3270dd25 | ||
|
|
680ab37542 | ||
|
|
b59ec4a0bf | ||
|
|
9f74f8521e | ||
|
|
e9a9455872 | ||
|
|
4584e8fa01 | ||
|
|
39831da3be | ||
|
|
33e71a248c | ||
|
|
44970c84f0 | ||
|
|
0f81d41935 | ||
|
|
c264a6025f | ||
|
|
e0ead91e51 | ||
|
|
c63421ceaf | ||
|
|
4ecd861aec | ||
|
|
6b2d6abbed | ||
|
|
f751c951cf | ||
|
|
68cb6a4d4e | ||
|
|
c207231dd1 | ||
|
|
c82b07a3f7 | ||
|
|
3ec9b62c10 | ||
|
|
3d9f2a2d8c | ||
|
|
52cf1b1db3 | ||
|
|
ac96c05a1d | ||
|
|
d9b53527d2 | ||
|
|
fc101f01ca | ||
|
|
f6487b65a6 | ||
|
|
78612ae03b | ||
|
|
80017d9eb0 | ||
|
|
7887e31447 | ||
|
|
ae319a1b17 | ||
|
|
90e89a7f94 | ||
|
|
840c5dcaa6 | ||
|
|
71133a3ab3 | ||
|
|
cfa4ac994f | ||
|
|
5de9fb3cd4 | ||
|
|
0d9a6987eb | ||
|
|
229bd8a745 | ||
|
|
2ded349f9c | ||
|
|
b27eb606ed | ||
|
|
412be77929 | ||
|
|
8d1aa2f38c | ||
|
|
8233131897 | ||
|
|
e7e1114237 | ||
|
|
25b7f8f1f9 | ||
|
|
d23264f254 | ||
|
|
c5099f9134 | ||
|
|
5295340649 | ||
|
|
0be98b1610 | ||
|
|
12557272f7 | ||
|
|
c666592142 | ||
|
|
bf865f930d | ||
|
|
9e932c9af9 | ||
|
|
dcc0fa9f9d | ||
|
|
26dfa3983f | ||
|
|
ed99ffaeec | ||
|
|
adf8f4bdb2 | ||
|
|
524eeac107 | ||
|
|
45830fbf0d | ||
|
|
ac0cf09335 | ||
|
|
5c77aacfe9 | ||
|
|
cc2548bcc7 | ||
|
|
63aaa8419d | ||
|
|
6db2028725 | ||
|
|
8dcc0a3cbe | ||
|
|
fee7801f98 | ||
|
|
735179047d | ||
|
|
1badf7955a | ||
|
|
5dd18690a0 | ||
|
|
c4b188caee | ||
|
|
5dccbbf343 | ||
|
|
5ec5e2f03c | ||
|
|
62ec748f57 | ||
|
|
d515337e36 | ||
|
|
8237f0085b | ||
|
|
7841de3bee | ||
|
|
2c3cef1c27 | ||
|
|
2d54bc4d17 | ||
|
|
664ab345fa | ||
|
|
2eee656dec | ||
|
|
28c135c26f | ||
|
|
01fdd229ab | ||
|
|
c1fefa3251 | ||
|
|
dcbb7847d0 | ||
|
|
0ee03468c6 | ||
|
|
ef7e29a0d8 | ||
|
|
c19d489852 | ||
|
|
3e56ebcfff | ||
|
|
189ada15fd | ||
|
|
50453d9d7e | ||
|
|
2740fea577 | ||
|
|
02a95b6f57 | ||
|
|
3aeeeb56ff | ||
|
|
9e6750f3d2 | ||
|
|
ff38f1cffc | ||
|
|
58f34bd0c6 | ||
|
|
44f89224b3 | ||
|
|
cf8b2c98c7 | ||
|
|
35b55d7221 | ||
|
|
9492defbe2 | ||
|
|
8fbc25a0d7 | ||
|
|
c05136ce50 | ||
|
|
60542aec87 | ||
|
|
6155b24c60 | ||
|
|
3b75cddefb | ||
|
|
75307b70c2 | ||
|
|
23591f385e | ||
|
|
82d8c363c5 | ||
|
|
b05d2d791e | ||
|
|
4df0ddcbd5 | ||
|
|
26ca566428 | ||
|
|
21caff6dd5 | ||
|
|
5d3cd92631 | ||
|
|
aa8521c70f | ||
|
|
1865bc6881 | ||
|
|
cd934ba80c | ||
|
|
5202df4407 | ||
|
|
99bcf30f77 | ||
|
|
56e8561e64 | ||
|
|
05fef8e463 | ||
|
|
6f5c61e442 | ||
|
|
1e28185ee9 | ||
|
|
4d7f72e3a1 | ||
|
|
6a414d5fb3 | ||
|
|
694c6e7214 | ||
|
|
e0e4e9778b | ||
|
|
7baa66b83e | ||
|
|
301bb2024e | ||
|
|
f7fce0100b | ||
|
|
af894485b4 | ||
|
|
2fb609fa98 | ||
|
|
c4627c3c04 | ||
|
|
5f0e8b8268 | ||
|
|
99c6bee64b | ||
|
|
853bfed89a | ||
|
|
86dc3fefc8 | ||
|
|
ea800b638d | ||
|
|
a2e69d8574 | ||
|
|
0178b322f9 | ||
|
|
acf3bf0959 | ||
|
|
49647c6267 | ||
|
|
0569b2bab3 | ||
|
|
badb5c1fba | ||
|
|
391935c90f | ||
|
|
9808f83913 | ||
|
|
ae33a89844 | ||
|
|
e0697eefa0 | ||
|
|
b50851726d | ||
|
|
55067c55a7 | ||
|
|
8d5612219c | ||
|
|
7bb0c588c1 | ||
|
|
d5857730d1 | ||
|
|
d2133da3ee | ||
|
|
c40d276770 | ||
|
|
77433f7e1d | ||
|
|
6ac719d74c | ||
|
|
f9bfd8adec | ||
|
|
1e91e1bd96 | ||
|
|
88d18fd5f3 | ||
|
|
98ae715905 | ||
|
|
e3ca1f09de | ||
|
|
8122c63ebc | ||
|
|
76308d5155 | ||
|
|
f5210c893e | ||
|
|
7c54065ed7 | ||
|
|
dd9836ea5f | ||
|
|
ac6cf3ef96 | ||
|
|
a87c3f28a2 | ||
|
|
afac8c384d | ||
|
|
b628349dc7 | ||
|
|
a4dc7a938a | ||
|
|
9dee4b1a7a | ||
|
|
2addea18ff | ||
|
|
8bbe71c8a2 | ||
|
|
bc9d3c5936 | ||
|
|
7db51e9029 | ||
|
|
a421ebf01c | ||
|
|
2e07e93a42 | ||
|
|
d9a8a7e0ac | ||
|
|
898fb3c6a8 | ||
|
|
a8aa035065 | ||
|
|
302651620b | ||
|
|
3f6bb4a929 | ||
|
|
ee49a7ed52 | ||
|
|
e4f92ad1a1 | ||
|
|
15affdbfda | ||
|
|
6886d6213a | ||
|
|
79dd4f73fe | ||
|
|
b536b73c97 | ||
|
|
a8ef8bdcb8 | ||
|
|
45538857cf | ||
|
|
2bf757a657 | ||
|
|
bbcfe56cfd | ||
|
|
360118634d | ||
|
|
9295bb42d6 | ||
|
|
505121f394 | ||
|
|
309e8bd183 | ||
|
|
801e4ad363 | ||
|
|
58cf46f435 | ||
|
|
1fca07546c | ||
|
|
9d2cb53cff | ||
|
|
4e95e32791 | ||
|
|
89fea30a3b | ||
|
|
e283c166de | ||
|
|
12bdd8742b | ||
|
|
38467b2e30 | ||
|
|
e46ac74ba4 | ||
|
|
07b390fccc | ||
|
|
c1b807dbff | ||
|
|
dc452feadd | ||
|
|
064f64f06d | ||
|
|
260e1d77f5 | ||
|
|
1eca55a386 | ||
|
|
c2602135ab | ||
|
|
b72d1af50a | ||
|
|
3e61bdc431 | ||
|
|
545592c472 | ||
|
|
de627a9b7c | ||
|
|
5fe4784cbc | ||
|
|
ff4f4255b3 | ||
|
|
c00f871b95 | ||
|
|
3a99010cdb | ||
|
|
d9246910b4 | ||
|
|
387bb29cbd |
27
.github/workflows/command-rebase.yml
vendored
Normal file
27
.github/workflows/command-rebase.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# This workflow is provided via the organization template repository
|
||||||
|
#
|
||||||
|
# https://github.com/nextcloud/.github
|
||||||
|
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
|
||||||
|
|
||||||
|
on:
|
||||||
|
issue_comment:
|
||||||
|
types: [ created ]
|
||||||
|
name: Automatic Rebase
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
rebase:
|
||||||
|
name: Rebase
|
||||||
|
# On pull requests and if the comment starts with `/rebase`
|
||||||
|
if: github.event.issue.pull_request != '' && startsWith(github.event.comment.body, '/rebase')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout the latest code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Automatic Rebase
|
||||||
|
uses: cirrus-actions/rebase@1.5
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
18
.github/workflows/rebase.yml
vendored
18
.github/workflows/rebase.yml
vendored
@@ -1,18 +0,0 @@
|
|||||||
on:
|
|
||||||
issue_comment:
|
|
||||||
types: [created]
|
|
||||||
name: Automatic Rebase
|
|
||||||
jobs:
|
|
||||||
rebase:
|
|
||||||
name: Rebase
|
|
||||||
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/rebase')
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout the latest code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Automatic Rebase
|
|
||||||
uses: cirrus-actions/rebase@1.3.1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
@@ -22,5 +22,6 @@ Icon=@APPLICATION_EXECUTABLE@
|
|||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
Icon[bg_BG]=@APPLICATION_ICON_NAME@
|
Icon[bg_BG]=@APPLICATION_ICON_NAME@
|
||||||
|
Name[bg_BG]=@APPLICATION_NAME@ десктоп клиент за синхронизиране
|
||||||
Comment[bg_BG]=@APPLICATION_NAME@ десктоп клиент за синхронизиране
|
Comment[bg_BG]=@APPLICATION_NAME@ десктоп клиент за синхронизиране
|
||||||
GenericName[bg_BG]=Синхронизиране на папка
|
GenericName[bg_BG]=Синхронизиране на папка
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ Icon=@APPLICATION_EXECUTABLE@
|
|||||||
|
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
Icon[de]=@APPLICATION_ICON_NAME@
|
Icon[de_DE]=@APPLICATION_ICON_NAME@
|
||||||
Name[de]=@APPLICATION_NAME@ Desktop
|
Name[de_DE]=@APPLICATION_NAME@ Client zur Desktop-Synchronisierung
|
||||||
Comment[de]=@APPLICATION_NAME@ Client zur Desktop-Synchronisierung
|
Comment[de_DE]=@APPLICATION_NAME@ Client zur Desktop-Synchronisierung
|
||||||
GenericName[de]=Ordner-Synchronisation
|
GenericName[de_DE]=Ordnersynchronisierung
|
||||||
|
|||||||
@@ -22,5 +22,6 @@ Icon=@APPLICATION_EXECUTABLE@
|
|||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
Icon[en_GB]=@APPLICATION_ICON_NAME@
|
Icon[en_GB]=@APPLICATION_ICON_NAME@
|
||||||
|
Name[en_GB]=@APPLICATION_NAME@ Desktop
|
||||||
Comment[en_GB]=@APPLICATION_NAME@ desktop synchronisation client
|
Comment[en_GB]=@APPLICATION_NAME@ desktop synchronisation client
|
||||||
GenericName[en_GB]=Folder Sync
|
GenericName[en_GB]=Folder Sync
|
||||||
|
|||||||
@@ -22,5 +22,6 @@ Icon=@APPLICATION_EXECUTABLE@
|
|||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
Icon[hr]=@APPLICATION_ICON_NAME@
|
Icon[hr]=@APPLICATION_ICON_NAME@
|
||||||
|
Name[hr]=@APPLICATION_NAME@ Desktop
|
||||||
Comment[hr]=@APPLICATION_NAME@ klijent za sinkronizaciju računala
|
Comment[hr]=@APPLICATION_NAME@ klijent za sinkronizaciju računala
|
||||||
GenericName[hr]=Sinkronizacija mapa
|
GenericName[hr]=Sinkronizacija mapa
|
||||||
|
|||||||
@@ -22,5 +22,6 @@ Icon=@APPLICATION_EXECUTABLE@
|
|||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
Icon[mk]=@APPLICATION_ICON_NAME@
|
Icon[mk]=@APPLICATION_ICON_NAME@
|
||||||
|
Name[mk]=@APPLICATION_NAME@ Десктоп
|
||||||
Comment[mk]=@APPLICATION_NAME@ клиент за синхронизација на компјутер
|
Comment[mk]=@APPLICATION_NAME@ клиент за синхронизација на компјутер
|
||||||
GenericName[mk]=Папка за синхронизација
|
GenericName[mk]=Папка за синхронизација
|
||||||
|
|||||||
@@ -22,5 +22,6 @@ Icon=@APPLICATION_EXECUTABLE@
|
|||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
Icon[ro]=@APPLICATION_ICON_NAME@
|
Icon[ro]=@APPLICATION_ICON_NAME@
|
||||||
|
Name[ro]=@Numele_aplicației@ Client de sincronizare pentru PC
|
||||||
Comment[ro]=@APPLICATION_NAME@ client de sincronizare pentru desktop
|
Comment[ro]=@APPLICATION_NAME@ client de sincronizare pentru desktop
|
||||||
GenericName[ro]=Sincronizare director
|
GenericName[ro]=Sincronizare director
|
||||||
|
|||||||
@@ -22,5 +22,6 @@ Icon=@APPLICATION_EXECUTABLE@
|
|||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
Icon[ru]=@APPLICATION_ICON_NAME@
|
Icon[ru]=@APPLICATION_ICON_NAME@
|
||||||
|
Name[ru]=@APPLICATION_NAME@ Desktop
|
||||||
Comment[ru]=Клиент синхронизации @APPLICATION_NAME@ для ПК
|
Comment[ru]=Клиент синхронизации @APPLICATION_NAME@ для ПК
|
||||||
GenericName[ru]=Синхронизация папок
|
GenericName[ru]=Синхронизация папок
|
||||||
|
|||||||
@@ -22,5 +22,6 @@ Icon=@APPLICATION_EXECUTABLE@
|
|||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
Icon[sc]=@NÙMENE_ICONA_APLICATZIONE@
|
Icon[sc]=@NÙMENE_ICONA_APLICATZIONE@
|
||||||
|
Name[sc]=@NÙMENE_APLICATZIONE@ Iscrivania
|
||||||
Comment[sc]=@NÙMENE_APLICATZIONE@ cliente de sincronizatzione iscrivania
|
Comment[sc]=@NÙMENE_APLICATZIONE@ cliente de sincronizatzione iscrivania
|
||||||
GenericName[sc]=Sincronizadore de cartellas
|
GenericName[sc]=Sincronizadore de cartellas
|
||||||
|
|||||||
27
.tx/nextcloud.client-desktop/th_translation
Normal file
27
.tx/nextcloud.client-desktop/th_translation
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Categories=Utility;X-SuSE-SyncUtility;
|
||||||
|
Type=Application
|
||||||
|
Exec=@APPLICATION_EXECUTABLE@
|
||||||
|
Name=@APPLICATION_NAME@ Desktop
|
||||||
|
Comment=@APPLICATION_NAME@ desktop synchronization client
|
||||||
|
GenericName=Folder Sync
|
||||||
|
Icon=@APPLICATION_ICON_NAME@
|
||||||
|
Keywords=@APPLICATION_NAME@;syncing;file;sharing;
|
||||||
|
X-GNOME-Autostart-Delay=3
|
||||||
|
MimeType=application/vnd.@APPLICATION_EXECUTABLE@;
|
||||||
|
Actions=Quit;
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
[Desktop Action Quit]
|
||||||
|
Exec=@APPLICATION_EXECUTABLE@ --quit
|
||||||
|
Name=Quit @APPLICATION_NAME@
|
||||||
|
Icon=@APPLICATION_EXECUTABLE@
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
Icon[th_TH]=@APPLICATION_ICON_NAME@
|
||||||
|
Name[th_TH]=@APPLICATION_NAME@ เดสก์ท็อป
|
||||||
|
Comment[th_TH]=ไคลเอ็นต์ซิงโครไนซ์ @APPLICATION_NAME@ บนเดสก์ท็อป
|
||||||
|
GenericName[th_TH]=ซิงค์โฟลเดอร์
|
||||||
27
.tx/nextcloud.client-desktop/vi_translation
Normal file
27
.tx/nextcloud.client-desktop/vi_translation
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Categories=Utility;X-SuSE-SyncUtility;
|
||||||
|
Type=Application
|
||||||
|
Exec=@APPLICATION_EXECUTABLE@
|
||||||
|
Name=@APPLICATION_NAME@ Desktop
|
||||||
|
Comment=@APPLICATION_NAME@ desktop synchronization client
|
||||||
|
GenericName=Folder Sync
|
||||||
|
Icon=@APPLICATION_ICON_NAME@
|
||||||
|
Keywords=@APPLICATION_NAME@;syncing;file;sharing;
|
||||||
|
X-GNOME-Autostart-Delay=3
|
||||||
|
MimeType=application/vnd.@APPLICATION_EXECUTABLE@;
|
||||||
|
Actions=Quit;
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
[Desktop Action Quit]
|
||||||
|
Exec=@APPLICATION_EXECUTABLE@ --quit
|
||||||
|
Name=Quit @APPLICATION_NAME@
|
||||||
|
Icon=@APPLICATION_EXECUTABLE@
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
Icon[vi]=@APPLICATION_ICON_NAME@
|
||||||
|
Name[vi]=@APPLICATION_NAME@ Máy tính
|
||||||
|
Comment[vi]=Ứng dụng đồng bộ @APPLICATION_NAME@ cho máy tính
|
||||||
|
GenericName[vi]=Đồng bộ thư mục
|
||||||
@@ -36,6 +36,8 @@ endif()
|
|||||||
|
|
||||||
set( CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules )
|
set( CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules )
|
||||||
|
|
||||||
|
include(ECMCoverageOption)
|
||||||
|
|
||||||
if(NOT CRASHREPORTER_EXECUTABLE)
|
if(NOT CRASHREPORTER_EXECUTABLE)
|
||||||
set(CRASHREPORTER_EXECUTABLE "${APPLICATION_EXECUTABLE}_crash_reporter")
|
set(CRASHREPORTER_EXECUTABLE "${APPLICATION_EXECUTABLE}_crash_reporter")
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ The :computer: Nextcloud Desktop Client is a tool to synchronize files from Next
|
|||||||
with your computer.
|
with your computer.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://nextcloud.com/wp-content/themes/next/assets/img/clients/desktop/macsettings.png?x16328" alt="Desktop Client on Mac OS]">
|
<img src="doc/images/main_dialog_christine.png" alt="Desktop Client on Windows" width="450">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## :blue_heart: :tada: Contributing
|
## :blue_heart: :tada: Contributing
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
set( MIRALL_VERSION_MAJOR 3 )
|
set( MIRALL_VERSION_MAJOR 3 )
|
||||||
set( MIRALL_VERSION_MINOR 2 )
|
set( MIRALL_VERSION_MINOR 3 )
|
||||||
set( MIRALL_VERSION_PATCH 81 )
|
set( MIRALL_VERSION_PATCH 6 )
|
||||||
set( MIRALL_VERSION_YEAR 2021 )
|
set( MIRALL_VERSION_YEAR 2021 )
|
||||||
set( MIRALL_SOVERSION 0 )
|
set( MIRALL_SOVERSION 0 )
|
||||||
|
|
||||||
# Minimum supported server version according to https://docs.nextcloud.com/server/latest/admin_manual/release_schedule.html
|
# Minimum supported server version according to https://docs.nextcloud.com/server/latest/admin_manual/release_schedule.html
|
||||||
set(NEXTCLOUD_SERVER_VERSION_MIN_SUPPORTED_MAJOR 19)
|
set(NEXTCLOUD_SERVER_VERSION_MIN_SUPPORTED_MAJOR 16)
|
||||||
set(NEXTCLOUD_SERVER_VERSION_MIN_SUPPORTED_MINOR 0)
|
set(NEXTCLOUD_SERVER_VERSION_MIN_SUPPORTED_MINOR 0)
|
||||||
set(NEXTCLOUD_SERVER_VERSION_MIN_SUPPORTED_PATCH 0)
|
set(NEXTCLOUD_SERVER_VERSION_MIN_SUPPORTED_PATCH 0)
|
||||||
|
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ OBS_PROJECT_BETA=home:ivaradi:beta
|
|||||||
OBS_PACKAGE=nextcloud-desktop
|
OBS_PACKAGE=nextcloud-desktop
|
||||||
|
|
||||||
if test "${DRONE_TARGET_BRANCH}" = "stable-2.6"; then
|
if test "${DRONE_TARGET_BRANCH}" = "stable-2.6"; then
|
||||||
UBUNTU_DISTRIBUTIONS="bionic focal groovy hirsute impish"
|
UBUNTU_DISTRIBUTIONS="bionic focal hirsute impish"
|
||||||
DEBIAN_DISTRIBUTIONS="buster stretch testing"
|
DEBIAN_DISTRIBUTIONS="buster stretch testing"
|
||||||
else
|
else
|
||||||
UBUNTU_DISTRIBUTIONS="focal groovy hirsute impish"
|
UBUNTU_DISTRIBUTIONS="focal hirsute impish"
|
||||||
DEBIAN_DISTRIBUTIONS="testing"
|
DEBIAN_DISTRIBUTIONS="testing"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
# Always enable the new 10.10 finder plugin if available
|
# Always enable the new 10.10 finder plugin if available
|
||||||
if [ -x "$(command -v pluginkit)" ]; then
|
if [ -x "$(command -v pluginkit)" ]; then
|
||||||
# add it to DB. This happens automatically too but we try to push it a bit harder for issue #3463
|
# add it to DB. This happens automatically too but we try to push it a bit harder for issue #3463
|
||||||
pluginkit -a "/Applications/@APPLICATION_EXECUTABLE@.app/Contents/PlugIns/FinderSyncExt.appex/"
|
pluginkit -a "/Applications/@APPLICATION_NAME@.app/Contents/PlugIns/FinderSyncExt.appex/"
|
||||||
# Since El Capitan we need to sleep #4650
|
# Since El Capitan we need to sleep #4650
|
||||||
sleep 10s
|
sleep 10s
|
||||||
# enable it
|
# enable it
|
||||||
|
|||||||
@@ -2,5 +2,6 @@
|
|||||||
|
|
||||||
# kill the old version. see issue #2044
|
# kill the old version. see issue #2044
|
||||||
killall @APPLICATION_EXECUTABLE@
|
killall @APPLICATION_EXECUTABLE@
|
||||||
|
killall @APPLICATION_NAME@
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
46
appveyor.ini
46
appveyor.ini
@@ -1,7 +1,6 @@
|
|||||||
[General]
|
[General]
|
||||||
Branch = master
|
Branch = master
|
||||||
ShallowClone = True
|
ShallowClone = True
|
||||||
Command=craft
|
|
||||||
|
|
||||||
# Variables defined here override the default value
|
# Variables defined here override the default value
|
||||||
# The variable names are casesensitive
|
# The variable names are casesensitive
|
||||||
@@ -13,33 +12,42 @@ CreateCache = False
|
|||||||
# Settings applicable for all Crafts matrices
|
# Settings applicable for all Crafts matrices
|
||||||
# Settings are Category/key=value
|
# Settings are Category/key=value
|
||||||
# Category is case sensitive
|
# Category is case sensitive
|
||||||
|
|
||||||
[GeneralSettings]
|
[GeneralSettings]
|
||||||
General/EMERGE_PKGDSTDIR=${Variables:APPVEYOR_BUILD_FOLDER}/binaries
|
|
||||||
Paths/python = C:\Python36
|
## This is the location of your python installation.
|
||||||
Paths/python27 = C:\Python27
|
## This value must be set.
|
||||||
|
Paths/Python = C:\Python39-x64
|
||||||
|
Paths/Python27 = C:\Python27-x64
|
||||||
|
|
||||||
|
Compile/BuildType = RelWithDebInfo
|
||||||
|
|
||||||
|
Compile/UseNinja = True
|
||||||
|
|
||||||
Paths/downloaddir = ${Variables:Root}\downloads
|
Paths/downloaddir = ${Variables:Root}\downloads
|
||||||
ShortPath/Enabled = False
|
ShortPath/Enabled = False
|
||||||
ShortPath/EnableJunctions = True
|
ShortPath/EnableJunctions = True
|
||||||
ShortPath/JunctionDir = C:\CM-SP\
|
ShortPath/JunctionDir = C:\CM-SP\
|
||||||
Packager/CacheDir = ${Variables:Root}\cache
|
|
||||||
|
; Packager/RepositoryUrl = https://files.kde.org/craft/
|
||||||
|
Packager/PackageType = NullsoftInstallerPackager
|
||||||
|
Packager/RepositoryUrl = http://ftp.acc.umu.se/mirror/kde.org/files/craft/master/
|
||||||
|
|
||||||
|
ContinuousIntegration/Enabled = True
|
||||||
|
|
||||||
|
## This option can be used to override the default make program
|
||||||
|
## change the value to the path of the executable you want to use instead.
|
||||||
|
Compile/MakeProgram = jom
|
||||||
|
|
||||||
Packager/UseCache = ${Variables:UseCache}
|
Packager/UseCache = ${Variables:UseCache}
|
||||||
Packager/CreateCache = ${Variables:CreateCache}
|
Packager/CreateCache = ${Variables:CreateCache}
|
||||||
; Packager/RepositoryUrl = https://files.kde.org/craft/
|
Packager/CacheDir = ${Variables:Root}\cache
|
||||||
Packager/PackageType = PortablePackager
|
|
||||||
Packager/RepositoryUrl = http://ftp.acc.umu.se/mirror/kde.org/files/craft/master/
|
|
||||||
Compile/BuildType = RelWithDebInfo
|
|
||||||
ContinuousIntegration/Enabled = True
|
|
||||||
|
|
||||||
[BlueprintSettings]
|
[BlueprintSettings]
|
||||||
# don't try to pip install on the ci
|
# don't try to pip install on the ci
|
||||||
python-modules.ignored = True
|
python-modules.ignored = True
|
||||||
|
nextcloud-client.buildTests = True
|
||||||
|
binary/mysql.useMariaDB = False
|
||||||
|
|
||||||
libs/qt5.version = 5.9.3
|
[windows-msvc2019_64-cl]
|
||||||
craft/craft-core.version = master
|
QtSDK/Compiler = msvc2019_64
|
||||||
|
General/ABI = windows-msvc2019_64-cl
|
||||||
[windows-msvc2017_64-cl]
|
|
||||||
General/ABI = windows-msvc2017_64-cl
|
|
||||||
|
|
||||||
[windows-msvc2017_32-cl]
|
|
||||||
General/ABI = windows-msvc2017_32-cl
|
|
||||||
|
|||||||
47
appveyor.yml
47
appveyor.yml
@@ -1,50 +1,49 @@
|
|||||||
version: '{build}-{branch}'
|
version: '{build}-{branch}'
|
||||||
|
|
||||||
|
image: Visual Studio 2019
|
||||||
|
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- master
|
- master
|
||||||
|
|
||||||
clone_depth: 50
|
clone_depth: 1
|
||||||
|
|
||||||
|
|
||||||
init:
|
init:
|
||||||
- ps: |
|
- ps: |
|
||||||
function craft($target) {
|
function craft() {
|
||||||
& C:\Python36\python.exe "C:\CraftMaster\CraftMaster\CraftMaster.py" --config "$env:APPVEYOR_BUILD_FOLDER\appveyor.ini" --variables "APPVEYOR_BUILD_FOLDER=$env:APPVEYOR_BUILD_FOLDER" --target $target -c $args
|
cmd /C "echo %PATH%"
|
||||||
|
& "C:\Python39-x64\python.exe" "C:\CraftMaster\CraftMaster\CraftMaster.py" --config "$env:APPVEYOR_BUILD_FOLDER\appveyor.ini" --variables "APPVEYOR_BUILD_FOLDER=$env:APPVEYOR_BUILD_FOLDER" --target $env:TARGET -c $args
|
||||||
if($LASTEXITCODE -ne 0) {exit $LASTEXITCODE}
|
if($LASTEXITCODE -ne 0) {exit $LASTEXITCODE}
|
||||||
}
|
}
|
||||||
|
function crafttests() {
|
||||||
|
cmd /C "echo %PATH%"
|
||||||
|
& "C:\Python39-x64\python.exe" "C:\CraftMaster\CraftMaster\CraftMaster.py" --config "$env:APPVEYOR_BUILD_FOLDER\appveyor.ini" --variables "APPVEYOR_BUILD_FOLDER=$env:APPVEYOR_BUILD_FOLDER" --target $env:TARGET -c $args
|
||||||
|
}
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- ps: |
|
- ps: |
|
||||||
#use cmd to silence powershell behaviour for stderr
|
#use cmd to silence powershell behaviour for stderr
|
||||||
& cmd /C "git clone -q --depth=1 git://anongit.kde.org/craftmaster.git C:\CraftMaster\CraftMaster 2>&1"
|
& cmd /C "git clone -q --depth=1 https://invent.kde.org/packaging/craftmaster.git C:\CraftMaster\CraftMaster 2>&1"
|
||||||
|
craft --add-blueprint-repository [git]https://github.com/nextcloud/desktop-client-blueprints.git
|
||||||
craft $env:TARGET -i craft
|
craft craft
|
||||||
craft $env:TARGET --install-deps owncloud-client
|
craft --install-deps nextcloud-client
|
||||||
|
craft nsis
|
||||||
|
|
||||||
build_script:
|
build_script:
|
||||||
- ps: |
|
- ps: |
|
||||||
craft $env:TARGET --no-cache --src-dir $env:APPVEYOR_BUILD_FOLDER owncloud-client
|
craft --src-dir $env:APPVEYOR_BUILD_FOLDER nextcloud-client
|
||||||
|
craft --package --src-dir $env:APPVEYOR_BUILD_FOLDER nextcloud-client
|
||||||
after_build:
|
cp C:\CraftMaster\windows-msvc2019_64-cl\tmp\*.7z $env:APPVEYOR_BUILD_FOLDER
|
||||||
- ps: |
|
cp C:\CraftMaster\windows-msvc2019_64-cl\tmp\*.exe $env:APPVEYOR_BUILD_FOLDER
|
||||||
craft $env:TARGET --src-dir $env:APPVEYOR_BUILD_FOLDER --package owncloud-client
|
|
||||||
|
|
||||||
|
|
||||||
on_finish:
|
|
||||||
- ps: |
|
|
||||||
Get-ChildItem $env:USERPROFILE\.craft\* | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
|
|
||||||
|
|
||||||
test_script:
|
test_script:
|
||||||
- ps: |
|
- ps: |
|
||||||
craft $env:TARGET --src-dir $env:APPVEYOR_BUILD_FOLDER --test owncloud-client
|
crafttests --test --src-dir $env:APPVEYOR_BUILD_FOLDER nextcloud-client
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
matrix:
|
matrix:
|
||||||
- TARGET: windows-msvc2017_32-cl
|
- TARGET: windows-msvc2019_64-cl
|
||||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
|
||||||
- TARGET: windows-msvc2017_64-cl
|
|
||||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
|
||||||
|
|
||||||
artifacts:
|
artifacts:
|
||||||
- path: binaries/*
|
- path: '*.7z'
|
||||||
|
- path: '*.exe'
|
||||||
|
|||||||
29
cmake/modules/ECMCoverageOption.cmake
Normal file
29
cmake/modules/ECMCoverageOption.cmake
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez <aleixpol@kde.org>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
#[=======================================================================[.rst:
|
||||||
|
ECMCoverageOption
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Allow users to easily enable GCov code coverage support.
|
||||||
|
|
||||||
|
Code coverage allows you to check how much of your codebase is covered by
|
||||||
|
your tests. This module makes it easy to build with support for
|
||||||
|
`GCov <https://gcc.gnu.org/onlinedocs/gcc/Gcov.html>`_.
|
||||||
|
|
||||||
|
When this module is included, a ``BUILD_COVERAGE`` option is added (default
|
||||||
|
OFF). Turning this option on enables GCC's coverage instrumentation, and
|
||||||
|
links against ``libgcov``.
|
||||||
|
|
||||||
|
Note that this will probably break the build if you are not using GCC.
|
||||||
|
|
||||||
|
Since 1.3.0.
|
||||||
|
#]=======================================================================]
|
||||||
|
|
||||||
|
option(BUILD_COVERAGE "Build the project with gcov support" OFF)
|
||||||
|
|
||||||
|
if(BUILD_COVERAGE)
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage")
|
||||||
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lgcov")
|
||||||
|
endif()
|
||||||
@@ -152,7 +152,7 @@ Conflict files are always created on the client and never on the server.
|
|||||||
"capabilities":{
|
"capabilities":{
|
||||||
"core":{
|
"core":{
|
||||||
"pollinterval":60,
|
"pollinterval":60,
|
||||||
"webdav-root":"remote.php/webdav"
|
"webdav-root":"remote.php/dav"
|
||||||
},
|
},
|
||||||
"dav":{
|
"dav":{
|
||||||
"chunking":"1.0"
|
"chunking":"1.0"
|
||||||
|
|||||||
@@ -32,10 +32,9 @@ itself. Should the silent update fail, the client offers a manual download.
|
|||||||
macOS
|
macOS
|
||||||
^^^^^
|
^^^^^
|
||||||
|
|
||||||
If a new update is available, the Nextcloud client initializes a pop-up dialog
|
There is no automatic updater on macOS. If a new update is available,
|
||||||
to alert you of the update and requesting that you update to the latest
|
the Nextcloud client initializes a pop-up dialog to alert you of the
|
||||||
version. Due to their use of the Sparkle frameworks, this is the default
|
update and requesting that you update to the latest version manually.
|
||||||
process for macOS applications.
|
|
||||||
|
|
||||||
Linux
|
Linux
|
||||||
^^^^^
|
^^^^^
|
||||||
@@ -96,14 +95,6 @@ To prevent automatic updates and disallow manual overrides:
|
|||||||
.. note:: branded clients have different key names
|
.. note:: branded clients have different key names
|
||||||
|
|
||||||
|
|
||||||
Preventing Automatic Updates in macOS Environments
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
You can disable the automatic update mechanism, in the macOS operating system,
|
|
||||||
by copying the file
|
|
||||||
``nextcloud.app/Contents/Resources/deny_autoupdate_com.nextcloud.desktopclient.plist``
|
|
||||||
to ``/Library/Preferences/com.nextcloud.desktopclient.plist``.
|
|
||||||
|
|
||||||
Preventing Automatic Updates in Linux Environments
|
Preventing Automatic Updates in Linux Environments
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|||||||
@@ -48,9 +48,9 @@ copyright = u'2013-2021, The Nextcloud developers'
|
|||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '3.2'
|
version = '3.3'
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = '3.2.81'
|
release = '3.3.6'
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
|
|||||||
BIN
doc/images/main_dialog_christine.png
Normal file
BIN
doc/images/main_dialog_christine.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 188 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 98 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 93 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 165 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 84 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 100 KiB |
@@ -8,7 +8,7 @@ There are clients for Linux, macOs, and Microsoft Windows.
|
|||||||
|
|
||||||
The currently supported server releases are the latest three stable versions
|
The currently supported server releases are the latest three stable versions
|
||||||
at time of publication. It means that the |version| release series is supporting
|
at time of publication. It means that the |version| release series is supporting
|
||||||
server major version 19, 20 and 21.
|
server major version 20, 21 and 22.
|
||||||
|
|
||||||
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
|
||||||
application: download the program and then double-click it to launch the
|
application: download the program and then double-click it to launch the
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ Improvements and New Features
|
|||||||
The |version| release of the Nextcloud desktop sync client has many new features and
|
The |version| release of the Nextcloud desktop sync client has many new features and
|
||||||
improvements.
|
improvements.
|
||||||
|
|
||||||
* Virtual Files on Windows
|
* Main dialog will be a regular window if tray icons are not available on the system.
|
||||||
* Support for the user status from the server
|
* Virtual files wil be optional. That enables the desktop client to run on older Windows versions.
|
||||||
* Many improvements to the sync engine
|
* Improvements for the virtual files on Windows
|
||||||
* Make the end-to-end encryption work more reliable
|
* Network traffic performance improvements
|
||||||
* Improve sync performance
|
* Improvements to the sync engine
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ the server URL.
|
|||||||
|
|
||||||
Other command line switches supported by ``nextcloudcmd`` include the following:
|
Other command line switches supported by ``nextcloudcmd`` include the following:
|
||||||
|
|
||||||
|
``--path``
|
||||||
|
Overrides default remote root folder to a specific subfolder on the server(e.g.: /Documents would sync the Documents subfolder on the server)
|
||||||
|
|
||||||
``--user``, ``-u`` ``[user]``
|
``--user``, ``-u`` ``[user]``
|
||||||
Use ``user`` as the login name.
|
Use ``user`` as the login name.
|
||||||
|
|
||||||
@@ -67,12 +70,6 @@ Other command line switches supported by ``nextcloudcmd`` include the following:
|
|||||||
``--httpproxy http://[user@pass:]<server>:<port>``
|
``--httpproxy http://[user@pass:]<server>:<port>``
|
||||||
Uses ``server`` as HTTP proxy.
|
Uses ``server`` as HTTP proxy.
|
||||||
|
|
||||||
``--nonshib``
|
|
||||||
Uses Non Shibboleth WebDAV Authentication
|
|
||||||
|
|
||||||
``--davpath [path]``
|
|
||||||
Overrides the WebDAV Path with ``path``
|
|
||||||
|
|
||||||
``--exclude [file]``
|
``--exclude [file]``
|
||||||
Exclude list file
|
Exclude list file
|
||||||
|
|
||||||
@@ -92,15 +89,15 @@ Credential Handling
|
|||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
$ nextcloudcmd /home/user/my_sync_folder https://carla:secret@server/nextcloud/remote.php/webdav/
|
$ nextcloudcmd /home/user/my_sync_folder https://carla:secret@server/nextcloud
|
||||||
|
|
||||||
To synchronize the Nextcloud directory ``Music`` to the local directory
|
To synchronize the Nextcloud directory ``Music`` to the local directory
|
||||||
``media/music``, through a proxy listening on port ``8080``, and on a gateway
|
``media/music``, through a proxy listening on port ``8080``, and on a gateway
|
||||||
machine using IP address ``192.168.178.1``, the command line would be::
|
machine using IP address ``192.168.178.1``, the command line would be::
|
||||||
|
|
||||||
$ nextcloudcmd --httpproxy http://192.168.178.1:8080 \
|
$ nextcloudcmd --httpproxy http://192.168.178.1:8080 --path /Music \
|
||||||
$HOME/media/music \
|
$HOME/media/music \
|
||||||
https://server/nextcloud/remote.php/webdav/Music
|
https://server/nextcloud
|
||||||
|
|
||||||
``nextcloudcmd`` will prompt for the user name and password, unless they have
|
``nextcloudcmd`` will prompt for the user name and password, unless they have
|
||||||
been specified on the command line or ``-n`` has been passed.
|
been specified on the command line or ``-n`` has been passed.
|
||||||
@@ -120,5 +117,5 @@ Example
|
|||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
$ nextcloudcmd /home/user/<my_sync_folder> \
|
$ nextcloudcmd --path /<Directory_that_has_been_created> /home/user/<my_sync_folder> \
|
||||||
https://<username>:<secret>@<server_address>/remote.php/webdav/<Directory_that_has_been_created>
|
https://<username>:<secret>@<server_address>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ Identifying Basic Functionality Problems
|
|||||||
|
|
||||||
For example, if your Nextcloud instance is installed at
|
For example, if your Nextcloud instance is installed at
|
||||||
``http://yourserver.com/nextcloud``, your WebDAV server address is
|
``http://yourserver.com/nextcloud``, your WebDAV server address is
|
||||||
``http://yourserver.com/nextcloud/remote.php/webdav``.
|
``http://yourserver.com/nextcloud/remote.php/dav``.
|
||||||
|
|
||||||
If you are prompted for your username and password but, after providing the
|
If you are prompted for your username and password but, after providing the
|
||||||
correct credentials, authentication fails, please ensure that your
|
correct credentials, authentication fails, please ensure that your
|
||||||
|
|||||||
@@ -24,11 +24,14 @@ The first parameter is the local directory. The second parameter is
|
|||||||
the server URL.
|
the server URL.
|
||||||
|
|
||||||
.. note:: Prior to the 1.6 release of nextcloudcmd, the tool only accepted
|
.. note:: Prior to the 1.6 release of nextcloudcmd, the tool only accepted
|
||||||
``nextcloud://`` or ``nextclouds://`` in place of ``http://`` and ``https://`` as
|
``owncloud://`` or ``ownclouds://`` in place of ``http://`` and ``https://`` as
|
||||||
a scheme. See ``Examples`` for details.
|
a scheme. See ``Examples`` for details.
|
||||||
|
|
||||||
OPTIONS
|
OPTIONS
|
||||||
=======
|
=======
|
||||||
|
``--path``
|
||||||
|
Overrides default remote root folder to a specific subfolder on the server(e.g.: /Documents would sync the Documents subfolder on the server)
|
||||||
|
|
||||||
``—user``, ``-u`` ``[user]``
|
``—user``, ``-u`` ``[user]``
|
||||||
Use ``user`` as the login name.
|
Use ``user`` as the login name.
|
||||||
|
|
||||||
@@ -50,12 +53,6 @@ OPTIONS
|
|||||||
``—httpproxy http://[user@pass:]<server>:<port>``
|
``—httpproxy http://[user@pass:]<server>:<port>``
|
||||||
Uses ``server`` as HTTP proxy.
|
Uses ``server`` as HTTP proxy.
|
||||||
|
|
||||||
``—nonshib``
|
|
||||||
Uses Non Shibboleth WebDAV Authentication
|
|
||||||
|
|
||||||
``—davpath [path]``
|
|
||||||
Overrides the WebDAV Path with ``path``
|
|
||||||
|
|
||||||
``—exclude [file]``
|
``—exclude [file]``
|
||||||
Exclude list file
|
Exclude list file
|
||||||
|
|
||||||
@@ -74,18 +71,18 @@ To synchronize the nextCloud directory ``Music`` to the local directory ``media/
|
|||||||
through a proxy listening on port ``8080`` on the gateway machine ``192.168.178.1``,
|
through a proxy listening on port ``8080`` on the gateway machine ``192.168.178.1``,
|
||||||
the command line would be::
|
the command line would be::
|
||||||
|
|
||||||
$ nextcloudcmd —httpproxy http://192.168.178.1:8080 \
|
$ nextcloudcmd —httpproxy http://192.168.178.1:8080 --path /Music \
|
||||||
$HOME/media/music \
|
$HOME/media/music \
|
||||||
https://server/nextcloud/remote.php/webdav/Music
|
https://server/nextcloud
|
||||||
|
|
||||||
``nextcloudcmd`` will enquire user name and password, unless they have
|
``nextcloudcmd`` will enquire user name and password, unless they have
|
||||||
been specified on the command line or ``-n`` (see `netrc(5)`) has been passed.
|
been specified on the command line or ``-n`` (see `netrc(5)`) has been passed.
|
||||||
|
|
||||||
Using the legacy scheme, it would be::
|
Using the legacy scheme, it would be::
|
||||||
|
|
||||||
$ nextcloudcmd —httpproxy http://192.168.178.1:8080 \
|
$ nextcloudcmd —httpproxy http://192.168.178.1:8080 --path /Music \
|
||||||
$HOME/media/music \
|
$HOME/media/music \
|
||||||
nextclouds://server/nextcloud/remote.php/webdav/Music
|
ownclouds://server/nextcloud
|
||||||
|
|
||||||
|
|
||||||
BUGS
|
BUGS
|
||||||
|
|||||||
@@ -6,5 +6,6 @@
|
|||||||
<file>theme/Style/Style.qml</file>
|
<file>theme/Style/Style.qml</file>
|
||||||
<file>theme/Style/qmldir</file>
|
<file>theme/Style/qmldir</file>
|
||||||
<file>src/gui/tray/ActivityActionButton.qml</file>
|
<file>src/gui/tray/ActivityActionButton.qml</file>
|
||||||
|
<file>src/gui/tray/ActivityItem.qml</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
121
src/cmd/cmd.cpp
121
src/cmd/cmd.cpp
@@ -66,6 +66,7 @@ struct CmdOptions
|
|||||||
{
|
{
|
||||||
QString source_dir;
|
QString source_dir;
|
||||||
QString target_url;
|
QString target_url;
|
||||||
|
QString remotePath = QStringLiteral("/");
|
||||||
QString config_directory;
|
QString config_directory;
|
||||||
QString user;
|
QString user;
|
||||||
QString password;
|
QString password;
|
||||||
@@ -75,10 +76,8 @@ struct CmdOptions
|
|||||||
bool useNetrc;
|
bool useNetrc;
|
||||||
bool interactive;
|
bool interactive;
|
||||||
bool ignoreHiddenFiles;
|
bool ignoreHiddenFiles;
|
||||||
bool nonShib;
|
|
||||||
QString exclude;
|
QString exclude;
|
||||||
QString unsyncedfolders;
|
QString unsyncedfolders;
|
||||||
QString davPath;
|
|
||||||
int restartTimes;
|
int restartTimes;
|
||||||
int downlimit;
|
int downlimit;
|
||||||
int uplimit;
|
int uplimit;
|
||||||
@@ -188,14 +187,13 @@ void help()
|
|||||||
std::cout << " --password, -p [pass] Use [pass] as password" << std::endl;
|
std::cout << " --password, -p [pass] Use [pass] as password" << std::endl;
|
||||||
std::cout << " -n Use netrc (5) for login" << std::endl;
|
std::cout << " -n Use netrc (5) for login" << std::endl;
|
||||||
std::cout << " --non-interactive Do not block execution with interaction" << std::endl;
|
std::cout << " --non-interactive Do not block execution with interaction" << std::endl;
|
||||||
std::cout << " --nonshib Use Non Shibboleth WebDAV authentication" << std::endl;
|
|
||||||
std::cout << " --davpath [path] Custom themed dav path, overrides --nonshib" << std::endl;
|
|
||||||
std::cout << " --max-sync-retries [n] Retries maximum n times (default to 3)" << std::endl;
|
std::cout << " --max-sync-retries [n] Retries maximum n times (default to 3)" << std::endl;
|
||||||
std::cout << " --uplimit [n] Limit the upload speed of files to n KB/s" << std::endl;
|
std::cout << " --uplimit [n] Limit the upload 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 << " --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 << " --logdebug More verbose logging" << std::endl;
|
std::cout << " --logdebug More verbose logging" << std::endl;
|
||||||
|
std::cout << " --path Path to a folder on a remote server" << std::endl;
|
||||||
std::cout << "" << std::endl;
|
std::cout << "" << std::endl;
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
@@ -263,10 +261,6 @@ void parseOptions(const QStringList &app_args, CmdOptions *options)
|
|||||||
options->exclude = it.next();
|
options->exclude = it.next();
|
||||||
} else if (option == "--unsyncedfolders" && !it.peekNext().startsWith("-")) {
|
} else if (option == "--unsyncedfolders" && !it.peekNext().startsWith("-")) {
|
||||||
options->unsyncedfolders = it.next();
|
options->unsyncedfolders = it.next();
|
||||||
} else if (option == "--nonshib") {
|
|
||||||
options->nonShib = true;
|
|
||||||
} else if (option == "--davpath" && !it.peekNext().startsWith("-")) {
|
|
||||||
options->davPath = it.next();
|
|
||||||
} else if (option == "--max-sync-retries" && !it.peekNext().startsWith("-")) {
|
} else if (option == "--max-sync-retries" && !it.peekNext().startsWith("-")) {
|
||||||
options->restartTimes = it.next().toInt();
|
options->restartTimes = it.next().toInt();
|
||||||
} else if (option == "--uplimit" && !it.peekNext().startsWith("-")) {
|
} else if (option == "--uplimit" && !it.peekNext().startsWith("-")) {
|
||||||
@@ -276,7 +270,10 @@ void parseOptions(const QStringList &app_args, CmdOptions *options)
|
|||||||
} else if (option == "--logdebug") {
|
} else if (option == "--logdebug") {
|
||||||
Logger::instance()->setLogFile("-");
|
Logger::instance()->setLogFile("-");
|
||||||
Logger::instance()->setLogDebug(true);
|
Logger::instance()->setLogDebug(true);
|
||||||
} else {
|
} else if (option == "--path" && !it.peekNext().startsWith("-")) {
|
||||||
|
options->remotePath = it.next();
|
||||||
|
}
|
||||||
|
else {
|
||||||
help();
|
help();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -312,6 +309,9 @@ void selectiveSyncFixup(OCC::SyncJournalDb *journal, const QStringList &newList)
|
|||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
SetDllDirectory(L"");
|
||||||
|
#endif
|
||||||
QCoreApplication app(argc, argv);
|
QCoreApplication app(argc, argv);
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
@@ -328,7 +328,6 @@ int main(int argc, char **argv)
|
|||||||
options.useNetrc = false;
|
options.useNetrc = false;
|
||||||
options.interactive = true;
|
options.interactive = true;
|
||||||
options.ignoreHiddenFiles = false; // Default is to sync hidden files
|
options.ignoreHiddenFiles = false; // Default is to sync hidden files
|
||||||
options.nonShib = false;
|
|
||||||
options.restartTimes = 3;
|
options.restartTimes = 3;
|
||||||
options.uplimit = 0;
|
options.uplimit = 0;
|
||||||
options.downlimit = 0;
|
options.downlimit = 0;
|
||||||
@@ -347,24 +346,15 @@ int main(int argc, char **argv)
|
|||||||
qFatal("Could not initialize account!");
|
qFatal("Could not initialize account!");
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
// check if the webDAV path was added to the url and append if not.
|
|
||||||
if (!options.target_url.endsWith("/")) {
|
if (options.target_url.contains("/webdav", Qt::CaseInsensitive) || options.target_url.contains("/dav", Qt::CaseInsensitive)) {
|
||||||
options.target_url.append("/");
|
qWarning("Dav or webdav in server URL.");
|
||||||
|
std::cerr << "Error! Please specify only the base URL of your host with username and password. Example:" << std::endl
|
||||||
|
<< "http(s)://username:password@cloud.example.com" << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.nonShib) {
|
QUrl hostUrl = QUrl::fromUserInput((options.target_url.endsWith(QLatin1Char('/')) || options.target_url.endsWith(QLatin1Char('\\'))) ? options.target_url.chopped(1) : options.target_url);
|
||||||
account->setNonShib(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!options.davPath.isEmpty()) {
|
|
||||||
account->setDavPath(options.davPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!options.target_url.contains(account->davPath())) {
|
|
||||||
options.target_url.append(account->davPath());
|
|
||||||
}
|
|
||||||
|
|
||||||
QUrl url = QUrl::fromUserInput(options.target_url);
|
|
||||||
|
|
||||||
// Order of retrieval attempt (later attempts override earlier ones):
|
// Order of retrieval attempt (later attempts override earlier ones):
|
||||||
// 1. From URL
|
// 1. From URL
|
||||||
@@ -372,8 +362,8 @@ int main(int argc, char **argv)
|
|||||||
// 3. From netrc (if enabled)
|
// 3. From netrc (if enabled)
|
||||||
// 4. From prompt (if interactive)
|
// 4. From prompt (if interactive)
|
||||||
|
|
||||||
QString user = url.userName();
|
QString user = hostUrl.userName();
|
||||||
QString password = url.password();
|
QString password = hostUrl.password();
|
||||||
|
|
||||||
if (!options.user.isEmpty()) {
|
if (!options.user.isEmpty()) {
|
||||||
user = options.user;
|
user = options.user;
|
||||||
@@ -386,7 +376,7 @@ int main(int argc, char **argv)
|
|||||||
if (options.useNetrc) {
|
if (options.useNetrc) {
|
||||||
NetrcParser parser;
|
NetrcParser parser;
|
||||||
if (parser.parse()) {
|
if (parser.parse()) {
|
||||||
NetrcParser::LoginPair pair = parser.find(url.host());
|
NetrcParser::LoginPair pair = parser.find(hostUrl.host());
|
||||||
user = pair.first;
|
user = pair.first;
|
||||||
password = pair.second;
|
password = pair.second;
|
||||||
}
|
}
|
||||||
@@ -404,24 +394,15 @@ int main(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// take the unmodified url to pass to csync_create()
|
|
||||||
QByteArray remUrl = options.target_url.toUtf8();
|
|
||||||
|
|
||||||
// Find the folder and the original owncloud url
|
// Find the folder and the original owncloud url
|
||||||
QStringList splitted = url.path().split("/" + account->davPath());
|
|
||||||
url.setPath(splitted.value(0));
|
|
||||||
|
|
||||||
url.setScheme(url.scheme().replace("owncloud", "http"));
|
hostUrl.setScheme(hostUrl.scheme().replace("owncloud", "http"));
|
||||||
|
|
||||||
QUrl credentialFreeUrl = url;
|
QUrl credentialFreeUrl = hostUrl;
|
||||||
credentialFreeUrl.setUserName(QString());
|
credentialFreeUrl.setUserName(QString());
|
||||||
credentialFreeUrl.setPassword(QString());
|
credentialFreeUrl.setPassword(QString());
|
||||||
|
|
||||||
// Remote folders typically start with a / and don't end with one
|
const QString folder = options.remotePath;
|
||||||
QString folder = "/" + splitted.value(1);
|
|
||||||
if (folder.endsWith("/") && folder != "/") {
|
|
||||||
folder.chop(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!options.proxy.isNull()) {
|
if (!options.proxy.isNull()) {
|
||||||
QString host;
|
QString host;
|
||||||
@@ -458,44 +439,36 @@ int main(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
account->setUrl(url);
|
account->setUrl(hostUrl);
|
||||||
account->setSslErrorHandler(sslErrorHandler);
|
account->setSslErrorHandler(sslErrorHandler);
|
||||||
|
|
||||||
// Perform a call to get the capabilities.
|
QEventLoop loop;
|
||||||
if (!options.nonShib) {
|
auto *job = new JsonApiJob(account, QLatin1String("ocs/v1.php/cloud/capabilities"));
|
||||||
// Do not do it if '--nonshib' was passed. This mean we should only connect to the 'nonshib'
|
QObject::connect(job, &JsonApiJob::jsonReceived, [&](const QJsonDocument &json) {
|
||||||
// dav endpoint. Since we do not get the capabilities, in that case, this has the additional
|
auto caps = json.object().value("ocs").toObject().value("data").toObject().value("capabilities").toObject();
|
||||||
// side effect that chunking-ng will be disabled. (because otherwise it would use the new
|
qDebug() << "Server capabilities" << caps;
|
||||||
// 'dav' endpoint instead of the nonshib one (which still use the old chunking)
|
account->setCapabilities(caps.toVariantMap());
|
||||||
|
account->setServerVersion(caps["core"].toObject()["status"].toObject()["version"].toString());
|
||||||
|
loop.quit();
|
||||||
|
});
|
||||||
|
job->start();
|
||||||
|
loop.exec();
|
||||||
|
|
||||||
QEventLoop loop;
|
if (job->reply()->error() != QNetworkReply::NoError){
|
||||||
auto *job = new JsonApiJob(account, QLatin1String("ocs/v1.php/cloud/capabilities"));
|
std::cout<<"Error connecting to server\n";
|
||||||
QObject::connect(job, &JsonApiJob::jsonReceived, [&](const QJsonDocument &json) {
|
return EXIT_FAILURE;
|
||||||
auto caps = json.object().value("ocs").toObject().value("data").toObject().value("capabilities").toObject();
|
|
||||||
qDebug() << "Server capabilities" << caps;
|
|
||||||
account->setCapabilities(caps.toVariantMap());
|
|
||||||
account->setServerVersion(caps["core"].toObject()["status"].toObject()["version"].toString());
|
|
||||||
loop.quit();
|
|
||||||
});
|
|
||||||
job->start();
|
|
||||||
loop.exec();
|
|
||||||
|
|
||||||
if (job->reply()->error() != QNetworkReply::NoError){
|
|
||||||
std::cout<<"Error connecting to server\n";
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
job = new JsonApiJob(account, QLatin1String("ocs/v1.php/cloud/user"));
|
|
||||||
QObject::connect(job, &JsonApiJob::jsonReceived, [&](const QJsonDocument &json) {
|
|
||||||
const QJsonObject data = json.object().value("ocs").toObject().value("data").toObject();
|
|
||||||
account->setDavUser(data.value("id").toString());
|
|
||||||
account->setDavDisplayName(data.value("display-name").toString());
|
|
||||||
loop.quit();
|
|
||||||
});
|
|
||||||
job->start();
|
|
||||||
loop.exec();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
job = new JsonApiJob(account, QLatin1String("ocs/v1.php/cloud/user"));
|
||||||
|
QObject::connect(job, &JsonApiJob::jsonReceived, [&](const QJsonDocument &json) {
|
||||||
|
const QJsonObject data = json.object().value("ocs").toObject().value("data").toObject();
|
||||||
|
account->setDavUser(data.value("id").toString());
|
||||||
|
account->setDavDisplayName(data.value("display-name").toString());
|
||||||
|
loop.quit();
|
||||||
|
});
|
||||||
|
job->start();
|
||||||
|
loop.exec();
|
||||||
|
|
||||||
// much lower age than the default since this utility is usually made to be run right after a change in the tests
|
// much lower age than the default since this utility is usually made to be run right after a change in the tests
|
||||||
SyncEngine::minimumFileAgeForUpload = std::chrono::milliseconds(0);
|
SyncEngine::minimumFileAgeForUpload = std::chrono::milliseconds(0);
|
||||||
|
|
||||||
|
|||||||
@@ -142,20 +142,26 @@ QByteArray makeChecksumHeader(const QByteArray &checksumType, const QByteArray &
|
|||||||
return header;
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray findBestChecksum(const QByteArray &checksums)
|
QByteArray findBestChecksum(const QByteArray &_checksums)
|
||||||
{
|
{
|
||||||
|
const auto checksums = QString::fromUtf8(_checksums);
|
||||||
int i = 0;
|
int i = 0;
|
||||||
// The order of the searches here defines the preference ordering.
|
// The order of the searches here defines the preference ordering.
|
||||||
if (-1 != (i = checksums.indexOf("SHA3-256:"))
|
if (-1 != (i = checksums.indexOf(QLatin1String("SHA3-256:"), 0, Qt::CaseInsensitive))
|
||||||
|| -1 != (i = checksums.indexOf("SHA256:"))
|
|| -1 != (i = checksums.indexOf(QLatin1String("SHA256:"), 0, Qt::CaseInsensitive))
|
||||||
|| -1 != (i = checksums.indexOf("SHA1:"))
|
|| -1 != (i = checksums.indexOf(QLatin1String("SHA1:"), 0, Qt::CaseInsensitive))
|
||||||
|| -1 != (i = checksums.indexOf("MD5:"))
|
|| -1 != (i = checksums.indexOf(QLatin1String("MD5:"), 0, Qt::CaseInsensitive))
|
||||||
|| -1 != (i = checksums.indexOf("Adler32:"))) {
|
|| -1 != (i = checksums.indexOf(QLatin1String("ADLER32:"), 0, Qt::CaseInsensitive))) {
|
||||||
// Now i is the start of the best checksum
|
// Now i is the start of the best checksum
|
||||||
// Grab it until the next space or end of string.
|
// Grab it until the next space or end of xml or end of string.
|
||||||
auto checksum = checksums.mid(i);
|
int end = _checksums.indexOf(' ', i);
|
||||||
return checksum.mid(0, checksum.indexOf(" "));
|
// workaround for https://github.com/owncloud/core/pull/38304
|
||||||
|
if (end == -1) {
|
||||||
|
end = _checksums.indexOf('<', i);
|
||||||
|
}
|
||||||
|
return _checksums.mid(i, end - i);
|
||||||
}
|
}
|
||||||
|
qCWarning(lcChecksums) << "Failed to parse" << _checksums;
|
||||||
return QByteArray();
|
return QByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,7 +334,7 @@ ComputeChecksum *ValidateChecksumHeader::prepareStart(const QByteArray &checksum
|
|||||||
|
|
||||||
if (!parseChecksumHeader(checksumHeader, &_expectedChecksumType, &_expectedChecksum)) {
|
if (!parseChecksumHeader(checksumHeader, &_expectedChecksumType, &_expectedChecksum)) {
|
||||||
qCWarning(lcChecksums) << "Checksum header malformed:" << checksumHeader;
|
qCWarning(lcChecksums) << "Checksum header malformed:" << checksumHeader;
|
||||||
emit validationFailed(tr("The checksum header is malformed."), {}, {}, _filePath);
|
emit validationFailed(tr("The checksum header is malformed."));
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,7 +347,6 @@ ComputeChecksum *ValidateChecksumHeader::prepareStart(const QByteArray &checksum
|
|||||||
|
|
||||||
void ValidateChecksumHeader::start(const QString &filePath, const QByteArray &checksumHeader)
|
void ValidateChecksumHeader::start(const QString &filePath, const QByteArray &checksumHeader)
|
||||||
{
|
{
|
||||||
_filePath = filePath;
|
|
||||||
if (auto calculator = prepareStart(checksumHeader))
|
if (auto calculator = prepareStart(checksumHeader))
|
||||||
calculator->start(filePath);
|
calculator->start(filePath);
|
||||||
}
|
}
|
||||||
@@ -356,11 +361,11 @@ void ValidateChecksumHeader::slotChecksumCalculated(const QByteArray &checksumTy
|
|||||||
const QByteArray &checksum)
|
const QByteArray &checksum)
|
||||||
{
|
{
|
||||||
if (checksumType != _expectedChecksumType) {
|
if (checksumType != _expectedChecksumType) {
|
||||||
emit validationFailed(tr("The checksum header contained an unknown checksum type '%1'").arg(QString::fromLatin1(_expectedChecksumType)), {}, {}, _filePath);
|
emit validationFailed(tr("The checksum header contained an unknown checksum type '%1'").arg(QString::fromLatin1(_expectedChecksumType)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (checksum != _expectedChecksum) {
|
if (checksum != _expectedChecksum) {
|
||||||
emit validationFailed(tr("The downloaded file does not match the checksum, it will be resumed. '%1' != '%2'").arg(QString::fromUtf8(_expectedChecksum), QString::fromUtf8(checksum)), checksumType, checksum, _filePath);
|
emit validationFailed(tr("The downloaded file does not match the checksum, it will be resumed. '%1' != '%2'").arg(QString::fromUtf8(_expectedChecksum), QString::fromUtf8(checksum)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
emit validated(checksumType, checksum);
|
emit validated(checksumType, checksum);
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ public:
|
|||||||
|
|
||||||
signals:
|
signals:
|
||||||
void validated(const QByteArray &checksumType, const QByteArray &checksum);
|
void validated(const QByteArray &checksumType, const QByteArray &checksum);
|
||||||
void validationFailed(const QString &errMsg, const QByteArray &checksumType, const QByteArray &checksum, const QString &filePath);
|
void validationFailed(const QString &errMsg);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void slotChecksumCalculated(const QByteArray &checksumType, const QByteArray &checksum);
|
void slotChecksumCalculated(const QByteArray &checksumType, const QByteArray &checksum);
|
||||||
@@ -173,8 +173,6 @@ private:
|
|||||||
|
|
||||||
QByteArray _expectedChecksumType;
|
QByteArray _expectedChecksumType;
|
||||||
QByteArray _expectedChecksum;
|
QByteArray _expectedChecksum;
|
||||||
|
|
||||||
QString _filePath;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ public:
|
|||||||
|
|
||||||
friend QDebug operator<<(QDebug &dbg, RemotePermissions p)
|
friend QDebug operator<<(QDebug &dbg, RemotePermissions p)
|
||||||
{
|
{
|
||||||
return dbg << p.toString().constData();
|
return dbg << p.toString();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ set(client_UI_SRCS
|
|||||||
proxyauthdialog.ui
|
proxyauthdialog.ui
|
||||||
mnemonicdialog.ui
|
mnemonicdialog.ui
|
||||||
tray/ActivityActionButton.qml
|
tray/ActivityActionButton.qml
|
||||||
|
tray/ActivityItem.qml
|
||||||
tray/Window.qml
|
tray/Window.qml
|
||||||
tray/UserLine.qml
|
tray/UserLine.qml
|
||||||
wizard/flow2authwidget.ui
|
wizard/flow2authwidget.ui
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ AccountPtr AccountManager::loadAccountHelper(QSettings &settings)
|
|||||||
qCInfo(lcAccountManager) << "Account for" << acc->url() << "using auth type" << authType;
|
qCInfo(lcAccountManager) << "Account for" << acc->url() << "using auth type" << authType;
|
||||||
|
|
||||||
acc->_serverVersion = settings.value(QLatin1String(serverVersionC)).toString();
|
acc->_serverVersion = settings.value(QLatin1String(serverVersionC)).toString();
|
||||||
acc->_davUser = settings.value(QLatin1String(davUserC)).toString();
|
acc->_davUser = settings.value(QLatin1String(davUserC), "").toString();
|
||||||
|
|
||||||
// We want to only restore settings for that auth type and the user value
|
// We want to only restore settings for that auth type and the user value
|
||||||
acc->_settingsMap.insert(QLatin1String(userC), settings.value(userC));
|
acc->_settingsMap.insert(QLatin1String(userC), settings.value(userC));
|
||||||
|
|||||||
@@ -511,20 +511,10 @@ void AccountSettings::slotSubfolderContextMenuRequested(const QModelIndex& index
|
|||||||
|
|
||||||
const auto path = rec.isValid() ? rec._path : remotePath;
|
const auto path = rec.isValid() ? rec._path : remotePath;
|
||||||
|
|
||||||
auto availability = folder->vfs().availability(path);
|
|
||||||
if (availability) {
|
|
||||||
ac = availabilityMenu->addAction(Utility::vfsCurrentAvailabilityText(*availability));
|
|
||||||
ac->setEnabled(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
ac = availabilityMenu->addAction(Utility::vfsPinActionText());
|
ac = availabilityMenu->addAction(Utility::vfsPinActionText());
|
||||||
ac->setEnabled(!availability || *availability != VfsItemAvailability::AlwaysLocal);
|
|
||||||
connect(ac, &QAction::triggered, this, [this, folder, path] { slotSetSubFolderAvailability(folder, path, PinState::AlwaysLocal); });
|
connect(ac, &QAction::triggered, this, [this, folder, path] { slotSetSubFolderAvailability(folder, path, PinState::AlwaysLocal); });
|
||||||
|
|
||||||
ac = availabilityMenu->addAction(Utility::vfsFreeSpaceActionText());
|
ac = availabilityMenu->addAction(Utility::vfsFreeSpaceActionText());
|
||||||
ac->setEnabled(!availability
|
|
||||||
|| !(*availability == VfsItemAvailability::OnlineOnly
|
|
||||||
|| *availability == VfsItemAvailability::AllDehydrated));
|
|
||||||
connect(ac, &QAction::triggered, this, [this, folder, path] { slotSetSubFolderAvailability(folder, path, PinState::OnlineOnly); });
|
connect(ac, &QAction::triggered, this, [this, folder, path] { slotSetSubFolderAvailability(folder, path, PinState::OnlineOnly); });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -594,20 +584,11 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos)
|
|||||||
|
|
||||||
if (folder->virtualFilesEnabled()) {
|
if (folder->virtualFilesEnabled()) {
|
||||||
auto availabilityMenu = menu->addMenu(tr("Availability"));
|
auto availabilityMenu = menu->addMenu(tr("Availability"));
|
||||||
auto availability = folder->vfs().availability(QString());
|
|
||||||
if (availability) {
|
|
||||||
ac = availabilityMenu->addAction(Utility::vfsCurrentAvailabilityText(*availability));
|
|
||||||
ac->setEnabled(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
ac = availabilityMenu->addAction(Utility::vfsPinActionText());
|
ac = availabilityMenu->addAction(Utility::vfsPinActionText());
|
||||||
ac->setEnabled(!availability || *availability != VfsItemAvailability::AlwaysLocal);
|
|
||||||
connect(ac, &QAction::triggered, this, [this]() { slotSetCurrentFolderAvailability(PinState::AlwaysLocal); });
|
connect(ac, &QAction::triggered, this, [this]() { slotSetCurrentFolderAvailability(PinState::AlwaysLocal); });
|
||||||
|
|
||||||
ac = availabilityMenu->addAction(Utility::vfsFreeSpaceActionText());
|
ac = availabilityMenu->addAction(Utility::vfsFreeSpaceActionText());
|
||||||
ac->setEnabled(!availability
|
|
||||||
|| !(*availability == VfsItemAvailability::OnlineOnly
|
|
||||||
|| *availability == VfsItemAvailability::AllDehydrated));
|
|
||||||
connect(ac, &QAction::triggered, this, [this]() { slotSetCurrentFolderAvailability(PinState::OnlineOnly); });
|
connect(ac, &QAction::triggered, this, [this]() { slotSetCurrentFolderAvailability(PinState::OnlineOnly); });
|
||||||
|
|
||||||
ac = menu->addAction(tr("Disable virtual file support …"));
|
ac = menu->addAction(tr("Disable virtual file support …"));
|
||||||
|
|||||||
@@ -227,17 +227,6 @@ void ConnectionValidator::checkServerCapabilities()
|
|||||||
job->setTimeout(timeoutToUseMsec);
|
job->setTimeout(timeoutToUseMsec);
|
||||||
QObject::connect(job, &JsonApiJob::jsonReceived, this, &ConnectionValidator::slotCapabilitiesRecieved);
|
QObject::connect(job, &JsonApiJob::jsonReceived, this, &ConnectionValidator::slotCapabilitiesRecieved);
|
||||||
job->start();
|
job->start();
|
||||||
|
|
||||||
// And we'll retrieve the ocs config in parallel
|
|
||||||
// note that 'this' might be destroyed before the job finishes, so intentionally not parented
|
|
||||||
auto configJob = new JsonApiJob(_account, QLatin1String("ocs/v1.php/config"));
|
|
||||||
configJob->setTimeout(timeoutToUseMsec);
|
|
||||||
auto account = _account; // capturing account by value will make it live long enough
|
|
||||||
QObject::connect(configJob, &JsonApiJob::jsonReceived, _account.data(),
|
|
||||||
[=](const QJsonDocument &json) {
|
|
||||||
ocsConfigReceived(json, account);
|
|
||||||
});
|
|
||||||
configJob->start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConnectionValidator::slotCapabilitiesRecieved(const QJsonDocument &json)
|
void ConnectionValidator::slotCapabilitiesRecieved(const QJsonDocument &json)
|
||||||
@@ -260,17 +249,6 @@ void ConnectionValidator::slotCapabilitiesRecieved(const QJsonDocument &json)
|
|||||||
fetchUser();
|
fetchUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConnectionValidator::ocsConfigReceived(const QJsonDocument &json, AccountPtr account)
|
|
||||||
{
|
|
||||||
QString host = json.object().value("ocs").toObject().value("data").toObject().value("host").toString();
|
|
||||||
if (host.isEmpty()) {
|
|
||||||
qCWarning(lcConnectionValidator) << "Could not extract 'host' from ocs config reply";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
qCInfo(lcConnectionValidator) << "Determined user-visible host to be" << host;
|
|
||||||
account->setUserVisibleHost(host);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConnectionValidator::fetchUser()
|
void ConnectionValidator::fetchUser()
|
||||||
{
|
{
|
||||||
auto *userInfo = new UserInfo(_accountState.data(), true, true, this);
|
auto *userInfo = new UserInfo(_accountState.data(), true, true, this);
|
||||||
|
|||||||
@@ -60,8 +60,7 @@ namespace OCC {
|
|||||||
+---------------------------+
|
+---------------------------+
|
||||||
|
|
|
|
||||||
+-> checkServerCapabilities --------------v (in parallel)
|
+-> checkServerCapabilities --------------v (in parallel)
|
||||||
JsonApiJob (cloud/capabilities) JsonApiJob (ocs/v1.php/config)
|
JsonApiJob (cloud/capabilities)
|
||||||
| +-> ocsConfigReceived
|
|
||||||
+-> slotCapabilitiesRecieved -+
|
+-> slotCapabilitiesRecieved -+
|
||||||
|
|
|
|
||||||
+---------------------------------+
|
+---------------------------------+
|
||||||
@@ -131,7 +130,6 @@ private:
|
|||||||
void reportResult(Status status);
|
void reportResult(Status status);
|
||||||
void checkServerCapabilities();
|
void checkServerCapabilities();
|
||||||
void fetchUser();
|
void fetchUser();
|
||||||
static void ocsConfigReceived(const QJsonDocument &json, AccountPtr account);
|
|
||||||
|
|
||||||
/** Sets the account's server version
|
/** Sets the account's server version
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ void Flow2Auth::fetchNewToken(const TokenAction action)
|
|||||||
|
|
||||||
// Step 1: Initiate a login, do an anonymous POST request
|
// Step 1: Initiate a login, do an anonymous POST request
|
||||||
QUrl url = Utility::concatUrlPath(_account->url().toString(), QLatin1String("/index.php/login/v2"));
|
QUrl url = Utility::concatUrlPath(_account->url().toString(), QLatin1String("/index.php/login/v2"));
|
||||||
|
_enforceHttps = url.scheme() == QStringLiteral("https");
|
||||||
|
|
||||||
// add 'Content-Length: 0' header (see https://github.com/nextcloud/desktop/issues/1473)
|
// add 'Content-Length: 0' header (see https://github.com/nextcloud/desktop/issues/1473)
|
||||||
QNetworkRequest req;
|
QNetworkRequest req;
|
||||||
@@ -98,6 +99,11 @@ void Flow2Auth::fetchNewToken(const TokenAction action)
|
|||||||
&& !json.isEmpty()) {
|
&& !json.isEmpty()) {
|
||||||
pollToken = json.value("poll").toObject().value("token").toString();
|
pollToken = json.value("poll").toObject().value("token").toString();
|
||||||
pollEndpoint = json.value("poll").toObject().value("endpoint").toString();
|
pollEndpoint = json.value("poll").toObject().value("endpoint").toString();
|
||||||
|
if (_enforceHttps && QUrl(pollEndpoint).scheme() != QStringLiteral("https")) {
|
||||||
|
qCWarning(lcFlow2auth) << "Can not poll endpoint because the returned url" << _pollEndpoint << "does not start with https";
|
||||||
|
emit result(Error, tr("The polling URL does not start with HTTPS despite the login URL started with HTTPS. Login will not be possible because this might be a security issue. Please contact your administrator."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
loginUrl = json["login"].toString();
|
loginUrl = json["login"].toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,6 +206,11 @@ void Flow2Auth::slotPollTimerTimeout()
|
|||||||
if (reply->error() == QNetworkReply::NoError && jsonParseError.error == QJsonParseError::NoError
|
if (reply->error() == QNetworkReply::NoError && jsonParseError.error == QJsonParseError::NoError
|
||||||
&& !json.isEmpty()) {
|
&& !json.isEmpty()) {
|
||||||
serverUrl = json["server"].toString();
|
serverUrl = json["server"].toString();
|
||||||
|
if (_enforceHttps && serverUrl.scheme() != QStringLiteral("https")) {
|
||||||
|
qCWarning(lcFlow2auth) << "Returned server url" << serverUrl << "does not start with https";
|
||||||
|
emit result(Error, tr("The returned server URL does not start with HTTPS despite the login URL started with HTTPS. Login will not be possible because this might be a security issue. Please contact your administrator."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
loginName = json["loginName"].toString();
|
loginName = json["loginName"].toString();
|
||||||
appPassword = json["appPassword"].toString();
|
appPassword = json["appPassword"].toString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ private:
|
|||||||
qint64 _secondsInterval;
|
qint64 _secondsInterval;
|
||||||
bool _isBusy;
|
bool _isBusy;
|
||||||
bool _hasToken;
|
bool _hasToken;
|
||||||
|
bool _enforceHttps = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace OCC
|
} // namespace OCC
|
||||||
|
|||||||
@@ -252,7 +252,7 @@ bool Folder::isBusy() const
|
|||||||
|
|
||||||
bool Folder::isSyncRunning() const
|
bool Folder::isSyncRunning() const
|
||||||
{
|
{
|
||||||
return _engine->isSyncRunning() || _vfs->isHydrating();
|
return _engine->isSyncRunning() || (_vfs && _vfs->isHydrating());
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Folder::remotePath() const
|
QString Folder::remotePath() const
|
||||||
|
|||||||
@@ -850,9 +850,6 @@ void FolderMan::slotEtagPollTimerTimeout()
|
|||||||
// Some folders need not to be checked because they use the push notifications
|
// Some folders need not to be checked because they use the push notifications
|
||||||
std::copy_if(folderMapValues.begin(), folderMapValues.end(), std::back_inserter(foldersToRun), [this](Folder *folder) -> bool {
|
std::copy_if(folderMapValues.begin(), folderMapValues.end(), std::back_inserter(foldersToRun), [this](Folder *folder) -> bool {
|
||||||
const auto account = folder->accountState()->account();
|
const auto account = folder->accountState()->account();
|
||||||
const auto capabilities = account->capabilities();
|
|
||||||
const auto pushNotifications = account->pushNotifications();
|
|
||||||
|
|
||||||
return !pushNotificationsFilesReady(account.data());
|
return !pushNotificationsFilesReady(account.data());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ void warnSystray()
|
|||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
SetDllDirectory(L"");
|
||||||
|
#endif
|
||||||
Q_INIT_RESOURCE(resources);
|
Q_INIT_RESOURCE(resources);
|
||||||
Q_INIT_RESOURCE(theme);
|
Q_INIT_RESOURCE(theme);
|
||||||
|
|
||||||
|
|||||||
@@ -323,12 +323,29 @@ void OwncloudSetupWizard::slotConnectToOCUrl(const QString &url)
|
|||||||
qCInfo(lcWizard) << "Connect to url: " << url;
|
qCInfo(lcWizard) << "Connect to url: " << url;
|
||||||
AbstractCredentials *creds = _ocWizard->getCredentials();
|
AbstractCredentials *creds = _ocWizard->getCredentials();
|
||||||
_ocWizard->account()->setCredentials(creds);
|
_ocWizard->account()->setCredentials(creds);
|
||||||
_ocWizard->setField(QLatin1String("OCUrl"), url);
|
|
||||||
_ocWizard->appendToConfigurationLog(tr("Trying to connect to %1 at %2 …")
|
|
||||||
.arg(Theme::instance()->appNameGUI())
|
|
||||||
.arg(url));
|
|
||||||
|
|
||||||
testOwnCloudConnect();
|
const auto fetchUserNameJob = new JsonApiJob(_ocWizard->account()->sharedFromThis(), QStringLiteral("/ocs/v1.php/cloud/user"));
|
||||||
|
connect(fetchUserNameJob, &JsonApiJob::jsonReceived, this, [this, url](const QJsonDocument &json, int statusCode) {
|
||||||
|
if (statusCode != 100) {
|
||||||
|
qCWarning(lcWizard) << "Could not fetch username.";
|
||||||
|
}
|
||||||
|
|
||||||
|
sender()->deleteLater();
|
||||||
|
|
||||||
|
const auto objData = json.object().value("ocs").toObject().value("data").toObject();
|
||||||
|
const auto userId = objData.value("id").toString("");
|
||||||
|
const auto displayName = objData.value("display-name").toString("");
|
||||||
|
_ocWizard->account()->setDavUser(userId);
|
||||||
|
_ocWizard->account()->setDavDisplayName(displayName);
|
||||||
|
|
||||||
|
_ocWizard->setField(QLatin1String("OCUrl"), url);
|
||||||
|
_ocWizard->appendToConfigurationLog(tr("Trying to connect to %1 at %2 …")
|
||||||
|
.arg(Theme::instance()->appNameGUI())
|
||||||
|
.arg(url));
|
||||||
|
|
||||||
|
testOwnCloudConnect();
|
||||||
|
});
|
||||||
|
fetchUserNameJob->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void OwncloudSetupWizard::testOwnCloudConnect()
|
void OwncloudSetupWizard::testOwnCloudConnect()
|
||||||
@@ -465,7 +482,7 @@ void OwncloudSetupWizard::slotCreateLocalAndRemoteFolders(const QString &localFo
|
|||||||
*
|
*
|
||||||
* Purpose: Don't rely on unsafe paths, be extra careful.
|
* Purpose: Don't rely on unsafe paths, be extra careful.
|
||||||
*
|
*
|
||||||
* Example: https://cloud.example.com/remote.php/webdav//
|
* Example: https://cloud.example.com/remote.php/dav//
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
qCInfo(lcWizard) << "Sanitize got URL path:" << QString(_ocWizard->account()->url().toString() + '/' + _ocWizard->account()->davPath() + remoteFolder);
|
qCInfo(lcWizard) << "Sanitize got URL path:" << QString(_ocWizard->account()->url().toString() + '/' + _ocWizard->account()->davPath() + remoteFolder);
|
||||||
|
|||||||
@@ -76,9 +76,9 @@ signals:
|
|||||||
void openHelp();
|
void openHelp();
|
||||||
void shutdown();
|
void shutdown();
|
||||||
|
|
||||||
Q_INVOKABLE void hideWindow();
|
void hideWindow();
|
||||||
Q_INVOKABLE void showWindow();
|
void showWindow();
|
||||||
Q_INVOKABLE void openShareDialog(const QString &sharePath, const QString &localPath);
|
void openShareDialog(const QString &sharePath, const QString &localPath);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void slotNewUserSelected();
|
void slotNewUserSelected();
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ Item {
|
|||||||
|
|
||||||
// label value
|
// label value
|
||||||
property string text: ""
|
property string text: ""
|
||||||
|
|
||||||
|
// font value
|
||||||
|
property var font: label.font
|
||||||
|
|
||||||
// icon value
|
// icon value
|
||||||
property string imageSource: ""
|
property string imageSource: ""
|
||||||
|
|||||||
260
src/gui/tray/ActivityItem.qml
Normal file
260
src/gui/tray/ActivityItem.qml
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
import QtQml 2.12
|
||||||
|
import QtQuick 2.9
|
||||||
|
import QtQuick.Controls 2.2
|
||||||
|
import QtQuick.Layouts 1.2
|
||||||
|
import Style 1.0
|
||||||
|
import com.nextcloud.desktopclient 1.0
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: activityMouseArea
|
||||||
|
enabled: (path !== "" || link !== "")
|
||||||
|
hoverEnabled: true
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 2
|
||||||
|
color: (parent.containsMouse ? Style.lightHover : "transparent")
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: activityItem
|
||||||
|
|
||||||
|
readonly property variant links: model.links
|
||||||
|
|
||||||
|
readonly property int itemIndex: model.index
|
||||||
|
|
||||||
|
width: activityMouseArea.width
|
||||||
|
height: Style.trayWindowHeaderHeight
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Accessible.role: Accessible.ListItem
|
||||||
|
Accessible.name: path !== "" ? qsTr("Open %1 locally").arg(displayPath)
|
||||||
|
: message
|
||||||
|
Accessible.onPressAction: activityMouseArea.clicked()
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: activityIcon
|
||||||
|
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
|
||||||
|
Layout.leftMargin: 8
|
||||||
|
Layout.preferredWidth: shareButton.icon.width
|
||||||
|
Layout.preferredHeight: shareButton.icon.height
|
||||||
|
verticalAlignment: Qt.AlignCenter
|
||||||
|
cache: true
|
||||||
|
source: icon
|
||||||
|
sourceSize.height: 64
|
||||||
|
sourceSize.width: 64
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: activityTextColumn
|
||||||
|
Layout.leftMargin: 8
|
||||||
|
Layout.topMargin: 4
|
||||||
|
Layout.bottomMargin: 4
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
spacing: 4
|
||||||
|
Layout.alignment: Qt.AlignLeft
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: activityTextTitle
|
||||||
|
text: (type === "Activity" || type === "Notification") ? subject : message
|
||||||
|
width: parent.width
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font.pixelSize: Style.topLinePixelSize
|
||||||
|
color: activityTextTitleColor
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: activityTextInfo
|
||||||
|
text: (type === "Sync") ? displayPath
|
||||||
|
: (type === "File") ? subject
|
||||||
|
: (type === "Notification") ? message
|
||||||
|
: ""
|
||||||
|
height: (text === "") ? 0 : activityTextTitle.height
|
||||||
|
width: parent.width
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font.pixelSize: Style.subLinePixelSize
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: activityTextDateTime
|
||||||
|
text: dateTime
|
||||||
|
height: (text === "") ? 0 : activityTextTitle.height
|
||||||
|
width: parent.width
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font.pixelSize: Style.subLinePixelSize
|
||||||
|
color: "#808080"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: activityActionsLayout
|
||||||
|
spacing: 0
|
||||||
|
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||||
|
Layout.minimumWidth: 28
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
function actionButtonIcon(actionIndex) {
|
||||||
|
const verb = String(model.links[actionIndex].verb);
|
||||||
|
if (verb === "WEB" && (model.objectType === "chat" || model.objectType === "call")) {
|
||||||
|
return "qrc:///client/theme/reply.svg";
|
||||||
|
} else if (verb === "DELETE") {
|
||||||
|
return "qrc:///client/theme/close.svg";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "qrc:///client/theme/confirm.svg";
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: activityItem.links.length > activityListView.maxActionButtons ? 1 : activityItem.links.length
|
||||||
|
|
||||||
|
ActivityActionButton {
|
||||||
|
id: activityActionButton
|
||||||
|
|
||||||
|
readonly property int actionIndex: model.index
|
||||||
|
readonly property bool primary: model.index === 0 && String(activityItem.links[actionIndex].verb) !== "DELETE"
|
||||||
|
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
text: !primary ? "" : activityItem.links[actionIndex].label
|
||||||
|
|
||||||
|
imageSource: !primary ? activityActionsLayout.actionButtonIcon(actionIndex) : ""
|
||||||
|
|
||||||
|
textColor: primary ? Style.ncBlue : "black"
|
||||||
|
textColorHovered: Style.lightHover
|
||||||
|
|
||||||
|
textBorderColor: Style.ncBlue
|
||||||
|
|
||||||
|
textBgColor: "transparent"
|
||||||
|
textBgColorHovered: Style.ncBlue
|
||||||
|
|
||||||
|
tooltipText: activityItem.links[actionIndex].label
|
||||||
|
|
||||||
|
Layout.minimumWidth: primary ? 80 : -1
|
||||||
|
Layout.minimumHeight: parent.height
|
||||||
|
|
||||||
|
Layout.preferredWidth: primary ? -1 : parent.height
|
||||||
|
|
||||||
|
onClicked: activityModel.triggerAction(activityItem.itemIndex, actionIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: moreActionsButton
|
||||||
|
|
||||||
|
Layout.preferredWidth: parent.height
|
||||||
|
Layout.preferredHeight: parent.height
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
|
||||||
|
flat: true
|
||||||
|
hoverEnabled: true
|
||||||
|
visible: activityItem.links.length > activityListView.maxActionButtons
|
||||||
|
display: AbstractButton.IconOnly
|
||||||
|
icon.source: "qrc:///client/theme/more.svg"
|
||||||
|
icon.color: "transparent"
|
||||||
|
background: Rectangle {
|
||||||
|
color: parent.hovered ? Style.lightHover : "transparent"
|
||||||
|
}
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.delay: 1000
|
||||||
|
ToolTip.text: qsTr("Show more actions")
|
||||||
|
|
||||||
|
Accessible.role: Accessible.Button
|
||||||
|
Accessible.name: qsTr("Show more actions")
|
||||||
|
Accessible.onPressAction: moreActionsButton.clicked()
|
||||||
|
|
||||||
|
onClicked: moreActionsButtonContextMenu.popup();
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: trayWindow
|
||||||
|
onActiveChanged: {
|
||||||
|
if (!trayWindow.active) {
|
||||||
|
moreActionsButtonContextMenu.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: activityListView
|
||||||
|
|
||||||
|
onMovementStarted: {
|
||||||
|
moreActionsButtonContextMenu.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Container {
|
||||||
|
id: moreActionsButtonContextMenuContainer
|
||||||
|
visible: moreActionsButtonContextMenu.opened
|
||||||
|
|
||||||
|
width: moreActionsButtonContextMenu.width
|
||||||
|
height: moreActionsButtonContextMenu.height
|
||||||
|
anchors.right: moreActionsButton.right
|
||||||
|
anchors.top: moreActionsButton.top
|
||||||
|
|
||||||
|
Menu {
|
||||||
|
id: moreActionsButtonContextMenu
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
// transform model to contain indexed actions with primary action filtered out
|
||||||
|
function actionListToContextMenuList(actionList) {
|
||||||
|
// early out with non-altered data
|
||||||
|
if (activityItem.links.length <= activityListView.maxActionButtons) {
|
||||||
|
return actionList;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add index to every action and filter 'primary' action out
|
||||||
|
var reducedActionList = actionList.reduce(function(reduced, action, index) {
|
||||||
|
if (!action.primary) {
|
||||||
|
var actionWithIndex = { actionIndex: index, label: action.label };
|
||||||
|
reduced.push(actionWithIndex);
|
||||||
|
}
|
||||||
|
return reduced;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
return reducedActionList;
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
id: moreActionsButtonContextMenuRepeater
|
||||||
|
|
||||||
|
model: moreActionsButtonContextMenu.actionListToContextMenuList(activityItem.links)
|
||||||
|
|
||||||
|
delegate: MenuItem {
|
||||||
|
id: moreActionsButtonContextMenuEntry
|
||||||
|
text: model.modelData.label
|
||||||
|
onTriggered: activityModel.triggerAction(activityItem.itemIndex, model.modelData.actionIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: shareButton
|
||||||
|
|
||||||
|
Layout.preferredWidth: (path === "") ? 0 : parent.height
|
||||||
|
Layout.preferredHeight: parent.height
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
flat: true
|
||||||
|
hoverEnabled: true
|
||||||
|
visible: (path === "") ? false : true
|
||||||
|
display: AbstractButton.IconOnly
|
||||||
|
icon.source: "qrc:///client/theme/share.svg"
|
||||||
|
icon.color: "transparent"
|
||||||
|
background: Rectangle {
|
||||||
|
color: parent.hovered ? Style.lightHover : "transparent"
|
||||||
|
}
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.delay: 1000
|
||||||
|
ToolTip.text: qsTr("Open share dialog")
|
||||||
|
onClicked: Systray.openShareDialog(displayPath,absolutePath)
|
||||||
|
|
||||||
|
Accessible.role: Accessible.Button
|
||||||
|
Accessible.name: qsTr("Share %1").arg(displayPath)
|
||||||
|
Accessible.onPressAction: shareButton.clicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,236 +14,237 @@ MenuItem {
|
|||||||
Accessible.role: Accessible.MenuItem
|
Accessible.role: Accessible.MenuItem
|
||||||
Accessible.name: qsTr("Account entry")
|
Accessible.name: qsTr("Account entry")
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
id: userLineLayout
|
id: userLineLayout
|
||||||
spacing: 0
|
spacing: 0
|
||||||
width: Style.currentAccountButtonWidth
|
anchors.fill: parent
|
||||||
height: parent.height
|
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
id: accountButton
|
id: accountButton
|
||||||
Layout.preferredWidth: (userLineLayout.width * (5/6))
|
Layout.preferredWidth: (userLineLayout.width * (5/6))
|
||||||
Layout.preferredHeight: (userLineLayout.height)
|
Layout.preferredHeight: (userLineLayout.height)
|
||||||
display: AbstractButton.IconOnly
|
display: AbstractButton.IconOnly
|
||||||
|
hoverEnabled: true
|
||||||
|
flat: true
|
||||||
|
|
||||||
|
Accessible.role: Accessible.Button
|
||||||
|
Accessible.name: qsTr("Switch to account") + " " + name
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
flat: true
|
onContainsMouseChanged: {
|
||||||
|
accountStatusIndicatorBackground.color = (containsMouse ? "#f6f6f6" : "white")
|
||||||
Accessible.role: Accessible.Button
|
|
||||||
Accessible.name: qsTr("Switch to account") + " " + name
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
onContainsMouseChanged: {
|
|
||||||
accountStatusIndicatorBackground.color = (containsMouse ? "#f6f6f6" : "white")
|
|
||||||
}
|
|
||||||
onClicked: {
|
|
||||||
if (!isCurrentUser) {
|
|
||||||
UserModel.switchCurrentUser(id)
|
|
||||||
} else {
|
|
||||||
accountMenu.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
onClicked: {
|
||||||
|
if (!isCurrentUser) {
|
||||||
background: Item {
|
UserModel.switchCurrentUser(id)
|
||||||
height: parent.height
|
} else {
|
||||||
width: userLine.menu ? userLine.menu.width : 0
|
accountMenu.close()
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 1
|
|
||||||
color: parent.parent.hovered ? Style.lightHover : "transparent"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
id: accountControlRowLayout
|
|
||||||
height: accountButton.height
|
|
||||||
width: accountButton.width
|
|
||||||
spacing: Style.userStatusSpacing
|
|
||||||
Image {
|
|
||||||
id: accountAvatar
|
|
||||||
Layout.leftMargin: 7
|
|
||||||
verticalAlignment: Qt.AlignCenter
|
|
||||||
cache: false
|
|
||||||
source: model.avatar != "" ? model.avatar : "image://avatars/fallbackBlack"
|
|
||||||
Layout.preferredHeight: Style.accountAvatarSize
|
|
||||||
Layout.preferredWidth: Style.accountAvatarSize
|
|
||||||
Rectangle {
|
|
||||||
id: accountStatusIndicatorBackground
|
|
||||||
visible: model.isConnected &&
|
|
||||||
model.serverHasUserStatus
|
|
||||||
width: accountStatusIndicator.sourceSize.width + 2
|
|
||||||
height: width
|
|
||||||
anchors.bottom: accountAvatar.bottom
|
|
||||||
anchors.right: accountAvatar.right
|
|
||||||
color: "white"
|
|
||||||
radius: width*0.5
|
|
||||||
}
|
|
||||||
Image {
|
|
||||||
id: accountStatusIndicator
|
|
||||||
visible: model.isConnected &&
|
|
||||||
model.serverHasUserStatus
|
|
||||||
source: model.statusIcon
|
|
||||||
cache: false
|
|
||||||
x: accountStatusIndicatorBackground.x + 1
|
|
||||||
y: accountStatusIndicatorBackground.y + 1
|
|
||||||
sourceSize.width: Style.accountAvatarStateIndicatorSize
|
|
||||||
sourceSize.height: Style.accountAvatarStateIndicatorSize
|
|
||||||
|
|
||||||
Accessible.role: Accessible.Indicator
|
|
||||||
Accessible.name: model.desktopNotificationsAllowed ? qsTr("Current user status is online") : qsTr("Current user status is do not disturb")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: accountLabels
|
|
||||||
Layout.leftMargin: Style.accountLabelsSpacing
|
|
||||||
Label {
|
|
||||||
id: accountUser
|
|
||||||
width: 128
|
|
||||||
text: name
|
|
||||||
elide: Text.ElideRight
|
|
||||||
color: "black"
|
|
||||||
font.pixelSize: Style.topLinePixelSize
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
Row {
|
|
||||||
visible: model.isConnected &&
|
|
||||||
model.serverHasUserStatus
|
|
||||||
width: Style.currentAccountLabelWidth + Style.userStatusEmojiSize
|
|
||||||
Label {
|
|
||||||
id: emoji
|
|
||||||
height: Style.topLinePixelSize
|
|
||||||
visible: model.statusEmoji !== ""
|
|
||||||
text: statusEmoji
|
|
||||||
topPadding: -Style.accountLabelsSpacing
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
id: message
|
|
||||||
height: Style.topLinePixelSize
|
|
||||||
visible: model.statusMessage !== ""
|
|
||||||
text: statusMessage
|
|
||||||
elide: Text.ElideRight
|
|
||||||
color: "black"
|
|
||||||
font.pixelSize: Style.subLinePixelSize
|
|
||||||
leftPadding: Style.accountLabelsSpacing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
id: accountServer
|
|
||||||
width: Style.currentAccountLabelWidth
|
|
||||||
height: Style.topLinePixelSize
|
|
||||||
text: server
|
|
||||||
elide: Text.ElideRight
|
|
||||||
color: "black"
|
|
||||||
font.pixelSize: Style.subLinePixelSize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // accountButton
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: userMoreButton
|
|
||||||
Layout.preferredWidth: (userLineLayout.width * (1/6))
|
|
||||||
Layout.preferredHeight: userLineLayout.height
|
|
||||||
flat: true
|
|
||||||
|
|
||||||
icon.source: "qrc:///client/theme/more.svg"
|
|
||||||
icon.color: "transparent"
|
|
||||||
|
|
||||||
Accessible.role: Accessible.ButtonMenu
|
|
||||||
Accessible.name: qsTr("Account actions")
|
|
||||||
Accessible.onPressAction: userMoreButtonMouseArea.clicked()
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: userMoreButtonMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
onClicked: {
|
|
||||||
if (userMoreButtonMenu.visible) {
|
|
||||||
userMoreButtonMenu.close()
|
|
||||||
} else {
|
|
||||||
userMoreButtonMenu.popup()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
background:
|
|
||||||
Rectangle {
|
|
||||||
color: userMoreButtonMouseArea.containsMouse ? "grey" : "transparent"
|
|
||||||
opacity: 0.2
|
|
||||||
height: userMoreButton.height - 2
|
|
||||||
y: userMoreButton.y + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Menu {
|
|
||||||
id: userMoreButtonMenu
|
|
||||||
width: 120
|
|
||||||
closePolicy: Menu.CloseOnPressOutsideParent | Menu.CloseOnEscape
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
border.color: Style.menuBorder
|
|
||||||
radius: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuItem {
|
|
||||||
text: model.isConnected ? qsTr("Log out") : qsTr("Log in")
|
|
||||||
font.pixelSize: Style.topLinePixelSize
|
|
||||||
hoverEnabled: true
|
|
||||||
onClicked: {
|
|
||||||
model.isConnected ? UserModel.logout(index) : UserModel.login(index)
|
|
||||||
accountMenu.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
background: Item {
|
|
||||||
height: parent.height
|
|
||||||
width: parent.menu.width
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 1
|
|
||||||
color: parent.parent.hovered ? Style.lightHover : "transparent"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Accessible.role: Accessible.Button
|
|
||||||
Accessible.name: model.isConnected ? qsTr("Log out") : qsTr("Log in")
|
|
||||||
|
|
||||||
onPressed: {
|
|
||||||
if (model.isConnected) {
|
|
||||||
UserModel.logout(index)
|
|
||||||
} else {
|
|
||||||
UserModel.login(index)
|
|
||||||
}
|
|
||||||
accountMenu.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuItem {
|
|
||||||
id: removeAccountButton
|
|
||||||
text: qsTr("Remove account")
|
|
||||||
font.pixelSize: Style.topLinePixelSize
|
|
||||||
hoverEnabled: true
|
|
||||||
onClicked: {
|
|
||||||
UserModel.removeAccount(index)
|
|
||||||
accountMenu.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
background: Item {
|
|
||||||
height: parent.height
|
|
||||||
width: parent.menu.width
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 1
|
|
||||||
color: parent.parent.hovered ? Style.lightHover : "transparent"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Accessible.role: Accessible.Button
|
|
||||||
Accessible.name: text
|
|
||||||
Accessible.onPressAction: removeAccountButton.clicked()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
background: Item {
|
||||||
|
height: parent.height
|
||||||
|
width: userLine.menu ? userLine.menu.width : 0
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 1
|
||||||
|
color: parent.parent.hovered ? Style.lightHover : "transparent"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: accountControlRowLayout
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: Style.userStatusSpacing
|
||||||
|
Image {
|
||||||
|
id: accountAvatar
|
||||||
|
Layout.leftMargin: 7
|
||||||
|
verticalAlignment: Qt.AlignCenter
|
||||||
|
cache: false
|
||||||
|
source: model.avatar != "" ? model.avatar : "image://avatars/fallbackBlack"
|
||||||
|
Layout.preferredHeight: Style.accountAvatarSize
|
||||||
|
Layout.preferredWidth: Style.accountAvatarSize
|
||||||
|
Rectangle {
|
||||||
|
id: accountStatusIndicatorBackground
|
||||||
|
visible: model.isConnected &&
|
||||||
|
model.serverHasUserStatus
|
||||||
|
width: accountStatusIndicator.sourceSize.width + 2
|
||||||
|
height: width
|
||||||
|
anchors.bottom: accountAvatar.bottom
|
||||||
|
anchors.right: accountAvatar.right
|
||||||
|
color: "white"
|
||||||
|
radius: width*0.5
|
||||||
|
}
|
||||||
|
Image {
|
||||||
|
id: accountStatusIndicator
|
||||||
|
visible: model.isConnected &&
|
||||||
|
model.serverHasUserStatus
|
||||||
|
source: model.statusIcon
|
||||||
|
cache: false
|
||||||
|
x: accountStatusIndicatorBackground.x + 1
|
||||||
|
y: accountStatusIndicatorBackground.y + 1
|
||||||
|
sourceSize.width: Style.accountAvatarStateIndicatorSize
|
||||||
|
sourceSize.height: Style.accountAvatarStateIndicatorSize
|
||||||
|
|
||||||
|
Accessible.role: Accessible.Indicator
|
||||||
|
Accessible.name: model.desktopNotificationsAllowed ? qsTr("Current user status is online") : qsTr("Current user status is do not disturb")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: accountLabels
|
||||||
|
Layout.leftMargin: Style.accountLabelsSpacing
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.maximumWidth: parent.width - Style.accountLabelsSpacing
|
||||||
|
Label {
|
||||||
|
id: accountUser
|
||||||
|
text: name
|
||||||
|
elide: Text.ElideRight
|
||||||
|
color: "black"
|
||||||
|
font.pixelSize: Style.topLinePixelSize
|
||||||
|
font.bold: true
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
Row {
|
||||||
|
visible: model.isConnected &&
|
||||||
|
model.serverHasUserStatus
|
||||||
|
width: parent.width
|
||||||
|
Label {
|
||||||
|
id: emoji
|
||||||
|
height: Style.topLinePixelSize
|
||||||
|
visible: model.statusEmoji !== ""
|
||||||
|
text: statusEmoji
|
||||||
|
topPadding: -Style.accountLabelsSpacing
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
id: message
|
||||||
|
height: Style.topLinePixelSize
|
||||||
|
width: parent.width - parent.spacing - emoji.width
|
||||||
|
visible: model.statusMessage !== ""
|
||||||
|
text: statusMessage
|
||||||
|
elide: Text.ElideRight
|
||||||
|
color: "black"
|
||||||
|
font.pixelSize: Style.subLinePixelSize
|
||||||
|
leftPadding: Style.accountLabelsSpacing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
id: accountServer
|
||||||
|
width: Style.currentAccountLabelWidth
|
||||||
|
height: Style.topLinePixelSize
|
||||||
|
text: server
|
||||||
|
elide: Text.ElideRight
|
||||||
|
color: "black"
|
||||||
|
font.pixelSize: Style.subLinePixelSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // accountButton
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: userMoreButton
|
||||||
|
Layout.preferredWidth: (userLineLayout.width * (1/6))
|
||||||
|
Layout.preferredHeight: userLineLayout.height
|
||||||
|
flat: true
|
||||||
|
|
||||||
|
icon.source: "qrc:///client/theme/more.svg"
|
||||||
|
icon.color: "transparent"
|
||||||
|
|
||||||
|
Accessible.role: Accessible.ButtonMenu
|
||||||
|
Accessible.name: qsTr("Account actions")
|
||||||
|
Accessible.onPressAction: userMoreButtonMouseArea.clicked()
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: userMoreButtonMouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
onClicked: {
|
||||||
|
if (userMoreButtonMenu.visible) {
|
||||||
|
userMoreButtonMenu.close()
|
||||||
|
} else {
|
||||||
|
userMoreButtonMenu.popup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
background:
|
||||||
|
Rectangle {
|
||||||
|
color: userMoreButtonMouseArea.containsMouse ? "grey" : "transparent"
|
||||||
|
opacity: 0.2
|
||||||
|
height: userMoreButton.height - 2
|
||||||
|
y: userMoreButton.y + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Menu {
|
||||||
|
id: userMoreButtonMenu
|
||||||
|
width: 120
|
||||||
|
closePolicy: Menu.CloseOnPressOutsideParent | Menu.CloseOnEscape
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
border.color: Style.menuBorder
|
||||||
|
radius: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
text: model.isConnected ? qsTr("Log out") : qsTr("Log in")
|
||||||
|
font.pixelSize: Style.topLinePixelSize
|
||||||
|
hoverEnabled: true
|
||||||
|
onClicked: {
|
||||||
|
model.isConnected ? UserModel.logout(index) : UserModel.login(index)
|
||||||
|
accountMenu.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Item {
|
||||||
|
height: parent.height
|
||||||
|
width: parent.menu.width
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 1
|
||||||
|
color: parent.parent.hovered ? Style.lightHover : "transparent"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Accessible.role: Accessible.Button
|
||||||
|
Accessible.name: model.isConnected ? qsTr("Log out") : qsTr("Log in")
|
||||||
|
|
||||||
|
onPressed: {
|
||||||
|
if (model.isConnected) {
|
||||||
|
UserModel.logout(index)
|
||||||
|
} else {
|
||||||
|
UserModel.login(index)
|
||||||
|
}
|
||||||
|
accountMenu.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
id: removeAccountButton
|
||||||
|
text: qsTr("Remove account")
|
||||||
|
font.pixelSize: Style.topLinePixelSize
|
||||||
|
hoverEnabled: true
|
||||||
|
onClicked: {
|
||||||
|
UserModel.removeAccount(index)
|
||||||
|
accountMenu.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Item {
|
||||||
|
height: parent.height
|
||||||
|
width: parent.menu.width
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 1
|
||||||
|
color: parent.parent.hovered ? Style.lightHover : "transparent"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Accessible.role: Accessible.Button
|
||||||
|
Accessible.name: text
|
||||||
|
Accessible.onPressAction: removeAccountButton.clicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} // MenuItem userLine
|
} // MenuItem userLine
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import QtQml 2.1
|
import QtQml 2.12
|
||||||
import QtQml.Models 2.1
|
import QtQml.Models 2.1
|
||||||
import QtQuick 2.9
|
import QtQuick 2.9
|
||||||
import QtQuick.Window 2.3
|
import QtQuick.Window 2.3
|
||||||
@@ -19,7 +19,7 @@ Window {
|
|||||||
width: Systray.useNormalWindow ? Style.trayWindowHeight : Style.trayWindowWidth
|
width: Systray.useNormalWindow ? Style.trayWindowHeight : Style.trayWindowWidth
|
||||||
height: Style.trayWindowHeight
|
height: Style.trayWindowHeight
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
flags: Qt.WindowTitleHint | Qt.CustomizeWindowHint | Qt.WindowCloseButtonHint | (Systray.useNormalWindow ? Qt.Dialog : Qt.Dialog | Qt.FramelessWindowHint)
|
flags: Systray.useNormalWindow ? Qt.Window : Qt.Dialog | Qt.FramelessWindowHint
|
||||||
|
|
||||||
|
|
||||||
readonly property int maxMenuHeight: Style.trayWindowHeight - Style.trayWindowHeaderHeight - 2 * Style.trayWindowBorderWidth
|
readonly property int maxMenuHeight: Style.trayWindowHeight - Style.trayWindowHeaderHeight - 2 * Style.trayWindowBorderWidth
|
||||||
@@ -30,7 +30,7 @@ Window {
|
|||||||
onActiveChanged: {
|
onActiveChanged: {
|
||||||
if (!Systray.useNormalWindow && !active) {
|
if (!Systray.useNormalWindow && !active) {
|
||||||
hide();
|
hide();
|
||||||
setClosed();
|
Systray.setClosed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,6 +362,9 @@ Window {
|
|||||||
spacing: 0
|
spacing: 0
|
||||||
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
|
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
|
||||||
Layout.leftMargin: Style.userStatusSpacing
|
Layout.leftMargin: Style.userStatusSpacing
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.maximumWidth: parent.width
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
id: currentAccountUser
|
id: currentAccountUser
|
||||||
Layout.alignment: Qt.AlignLeft | Qt.AlignBottom
|
Layout.alignment: Qt.AlignLeft | Qt.AlignBottom
|
||||||
@@ -372,11 +375,14 @@ Window {
|
|||||||
font.pixelSize: Style.topLinePixelSize
|
font.pixelSize: Style.topLinePixelSize
|
||||||
font.bold: true
|
font.bold: true
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
id: currentUserStatus
|
id: currentUserStatus
|
||||||
visible: UserModel.currentUser.isConnected &&
|
visible: UserModel.currentUser.isConnected &&
|
||||||
UserModel.currentUser.serverHasUserStatus
|
UserModel.currentUser.serverHasUserStatus
|
||||||
spacing: Style.accountLabelsSpacing
|
spacing: Style.accountLabelsSpacing
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
id: emoji
|
id: emoji
|
||||||
visible: UserModel.currentUser.statusEmoji !== ""
|
visible: UserModel.currentUser.statusEmoji !== ""
|
||||||
@@ -386,6 +392,7 @@ Window {
|
|||||||
Label {
|
Label {
|
||||||
id: message
|
id: message
|
||||||
Layout.alignment: Qt.AlignLeft | Qt.AlignBottom
|
Layout.alignment: Qt.AlignLeft | Qt.AlignBottom
|
||||||
|
Layout.fillWidth: true
|
||||||
visible: UserModel.currentUser.statusMessage !== ""
|
visible: UserModel.currentUser.statusMessage !== ""
|
||||||
width: Style.currentAccountLabelWidth
|
width: Style.currentAccountLabelWidth
|
||||||
text: UserModel.currentUser.statusMessage !== ""
|
text: UserModel.currentUser.statusMessage !== ""
|
||||||
@@ -572,296 +579,11 @@ Window {
|
|||||||
|
|
||||||
model: activityModel
|
model: activityModel
|
||||||
|
|
||||||
delegate: RowLayout {
|
delegate: ActivityItem {
|
||||||
id: activityItem
|
width: activityListView.width
|
||||||
|
|
||||||
readonly property variant links: model.links
|
|
||||||
|
|
||||||
readonly property int itemIndex: model.index
|
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
height: Style.trayWindowHeaderHeight
|
height: Style.trayWindowHeaderHeight
|
||||||
spacing: 0
|
onClicked: activityModel.triggerDefaultAction(model.index)
|
||||||
|
|
||||||
Accessible.role: Accessible.ListItem
|
|
||||||
Accessible.name: path !== "" ? qsTr("Open %1 locally").arg(displayPath)
|
|
||||||
: message
|
|
||||||
Accessible.onPressAction: activityMouseArea.clicked()
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: activityMouseArea
|
|
||||||
enabled: (path !== "" || link !== "")
|
|
||||||
anchors.left: activityItem.left
|
|
||||||
anchors.right: activityActionsLayout.right
|
|
||||||
height: parent.height
|
|
||||||
anchors.margins: 2
|
|
||||||
hoverEnabled: true
|
|
||||||
onClicked: activityModel.triggerDefaultAction(model.index)
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
color: (parent.containsMouse ? Style.lightHover : "transparent")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Image {
|
|
||||||
id: activityIcon
|
|
||||||
anchors.left: activityItem.left
|
|
||||||
anchors.leftMargin: 8
|
|
||||||
anchors.rightMargin: 8
|
|
||||||
Layout.preferredWidth: shareButton.icon.width
|
|
||||||
Layout.preferredHeight: shareButton.icon.height
|
|
||||||
verticalAlignment: Qt.AlignCenter
|
|
||||||
cache: true
|
|
||||||
source: icon
|
|
||||||
sourceSize.height: 64
|
|
||||||
sourceSize.width: 64
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: activityTextColumn
|
|
||||||
anchors.left: activityIcon.right
|
|
||||||
anchors.right: activityActionsLayout.left
|
|
||||||
anchors.leftMargin: 8
|
|
||||||
spacing: 4
|
|
||||||
Layout.alignment: Qt.AlignLeft
|
|
||||||
Text {
|
|
||||||
id: activityTextTitle
|
|
||||||
text: (type === "Activity" || type === "Notification") ? subject : message
|
|
||||||
width: parent.width
|
|
||||||
elide: Text.ElideRight
|
|
||||||
font.pixelSize: Style.topLinePixelSize
|
|
||||||
color: activityTextTitleColor
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
id: activityTextInfo
|
|
||||||
text: (type === "Sync") ? displayPath
|
|
||||||
: (type === "File") ? subject
|
|
||||||
: (type === "Notification") ? message
|
|
||||||
: ""
|
|
||||||
height: (text === "") ? 0 : activityTextTitle.height
|
|
||||||
width: parent.width
|
|
||||||
elide: Text.ElideRight
|
|
||||||
font.pixelSize: Style.subLinePixelSize
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
id: activityTextDateTime
|
|
||||||
text: dateTime
|
|
||||||
height: (text === "") ? 0 : activityTextTitle.height
|
|
||||||
width: parent.width
|
|
||||||
elide: Text.ElideRight
|
|
||||||
font.pixelSize: Style.subLinePixelSize
|
|
||||||
color: "#808080"
|
|
||||||
}
|
|
||||||
|
|
||||||
ToolTip {
|
|
||||||
id: toolTip
|
|
||||||
visible: activityMouseArea.containsMouse
|
|
||||||
text: activityTextTitle.text + ((activityTextInfo.text !== "") ? "\n\n" + activityTextInfo.text : "")
|
|
||||||
delay: 250
|
|
||||||
timeout: 10000
|
|
||||||
// Can be dropped on more recent Qt, but on 5.12 it doesn't wrap...
|
|
||||||
contentItem: Text {
|
|
||||||
text: toolTip.text
|
|
||||||
font: toolTip.font
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
color: toolTip.palette.toolTipText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RowLayout {
|
|
||||||
id: activityActionsLayout
|
|
||||||
anchors.right: activityItem.right
|
|
||||||
spacing: 0
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
|
|
||||||
function actionButtonIcon(actionIndex) {
|
|
||||||
const verb = String(model.links[actionIndex].verb);
|
|
||||||
if (verb === "WEB" && (model.objectType === "chat" || model.objectType === "call")) {
|
|
||||||
return "qrc:///client/theme/reply.svg";
|
|
||||||
} else if (verb === "DELETE") {
|
|
||||||
return "qrc:///client/theme/close.svg";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "qrc:///client/theme/confirm.svg";
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: activityItem.links.length > activityListView.maxActionButtons ? 1 : activityItem.links.length
|
|
||||||
|
|
||||||
ActivityActionButton {
|
|
||||||
id: activityActionButton
|
|
||||||
|
|
||||||
readonly property int actionIndex: model.index
|
|
||||||
readonly property bool primary: model.index === 0 && String(activityItem.links[actionIndex].verb) !== "DELETE"
|
|
||||||
|
|
||||||
height: activityItem.height
|
|
||||||
|
|
||||||
text: !primary ? "" : activityItem.links[actionIndex].label
|
|
||||||
|
|
||||||
imageSource: !primary ? activityActionsLayout.actionButtonIcon(actionIndex) : ""
|
|
||||||
|
|
||||||
textColor: primary ? Style.ncBlue : "black"
|
|
||||||
textColorHovered: Style.lightHover
|
|
||||||
|
|
||||||
textBorderColor: Style.ncBlue
|
|
||||||
|
|
||||||
textBgColor: "transparent"
|
|
||||||
textBgColorHovered: Style.ncBlue
|
|
||||||
|
|
||||||
tooltipText: activityItem.links[actionIndex].label
|
|
||||||
|
|
||||||
Layout.minimumWidth: primary ? 80 : -1
|
|
||||||
Layout.minimumHeight: parent.height
|
|
||||||
|
|
||||||
Layout.preferredWidth: primary ? -1 : parent.height
|
|
||||||
|
|
||||||
onClicked: activityModel.triggerAction(activityItem.itemIndex, actionIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: moreActionsButton
|
|
||||||
|
|
||||||
Layout.preferredWidth: parent.height
|
|
||||||
Layout.preferredHeight: parent.height
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
|
|
||||||
flat: true
|
|
||||||
hoverEnabled: true
|
|
||||||
visible: activityItem.links.length > activityListView.maxActionButtons
|
|
||||||
display: AbstractButton.IconOnly
|
|
||||||
icon.source: "qrc:///client/theme/more.svg"
|
|
||||||
icon.color: "transparent"
|
|
||||||
background: Rectangle {
|
|
||||||
color: parent.hovered ? Style.lightHover : "transparent"
|
|
||||||
}
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.delay: 1000
|
|
||||||
ToolTip.text: qsTr("Show more actions")
|
|
||||||
|
|
||||||
Accessible.role: Accessible.Button
|
|
||||||
Accessible.name: qsTr("Show more actions")
|
|
||||||
Accessible.onPressAction: moreActionsButton.clicked()
|
|
||||||
|
|
||||||
onClicked: moreActionsButtonContextMenu.popup();
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: trayWindow
|
|
||||||
onActiveChanged: {
|
|
||||||
if (!trayWindow.active) {
|
|
||||||
moreActionsButtonContextMenu.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: activityListView
|
|
||||||
|
|
||||||
onMovementStarted: {
|
|
||||||
moreActionsButtonContextMenu.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Container {
|
|
||||||
id: moreActionsButtonContextMenuContainer
|
|
||||||
visible: moreActionsButtonContextMenu.opened
|
|
||||||
|
|
||||||
width: moreActionsButtonContextMenu.width
|
|
||||||
height: moreActionsButtonContextMenu.height
|
|
||||||
anchors.right: moreActionsButton.right
|
|
||||||
anchors.top: moreActionsButton.top
|
|
||||||
|
|
||||||
Menu {
|
|
||||||
id: moreActionsButtonContextMenu
|
|
||||||
anchors.centerIn: parent
|
|
||||||
|
|
||||||
// transform model to contain indexed actions with primary action filtered out
|
|
||||||
function actionListToContextMenuList(actionList) {
|
|
||||||
// early out with non-altered data
|
|
||||||
if (activityItem.links.length <= activityListView.maxActionButtons) {
|
|
||||||
return actionList;
|
|
||||||
}
|
|
||||||
|
|
||||||
// add index to every action and filter 'primary' action out
|
|
||||||
var reducedActionList = actionList.reduce(function(reduced, action, index) {
|
|
||||||
if (!action.primary) {
|
|
||||||
var actionWithIndex = { actionIndex: index, label: action.label };
|
|
||||||
reduced.push(actionWithIndex);
|
|
||||||
}
|
|
||||||
return reduced;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
|
|
||||||
return reducedActionList;
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
id: moreActionsButtonContextMenuRepeater
|
|
||||||
|
|
||||||
model: moreActionsButtonContextMenu.actionListToContextMenuList(activityItem.links)
|
|
||||||
|
|
||||||
delegate: MenuItem {
|
|
||||||
id: moreActionsButtonContextMenuEntry
|
|
||||||
readonly property int actionIndex: model.modelData.actionIndex
|
|
||||||
readonly property string label: model.modelData.label
|
|
||||||
text: label
|
|
||||||
onTriggered: activityModel.triggerAction(activityItem.itemIndex, actionIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: shareButton
|
|
||||||
|
|
||||||
Layout.preferredWidth: (path === "") ? 0 : parent.height
|
|
||||||
Layout.preferredHeight: parent.height
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
flat: true
|
|
||||||
hoverEnabled: true
|
|
||||||
visible: (path === "") ? false : true
|
|
||||||
display: AbstractButton.IconOnly
|
|
||||||
icon.source: "qrc:///client/theme/share.svg"
|
|
||||||
icon.color: "transparent"
|
|
||||||
background: Rectangle {
|
|
||||||
color: parent.hovered ? Style.lightHover : "transparent"
|
|
||||||
}
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.delay: 1000
|
|
||||||
ToolTip.text: qsTr("Open share dialog")
|
|
||||||
onClicked: Systray.openShareDialog(displayPath,absolutePath)
|
|
||||||
|
|
||||||
Accessible.role: Accessible.Button
|
|
||||||
Accessible.name: qsTr("Share %1").arg(displayPath)
|
|
||||||
Accessible.onPressAction: shareButton.clicked()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*add: Transition {
|
|
||||||
NumberAnimation { properties: "y"; from: -60; duration: 100; easing.type: Easing.Linear }
|
|
||||||
}
|
|
||||||
|
|
||||||
remove: Transition {
|
|
||||||
NumberAnimation { property: "opacity"; from: 1.0; to: 0; duration: 100 }
|
|
||||||
}
|
|
||||||
|
|
||||||
removeDisplaced: Transition {
|
|
||||||
SequentialAnimation {
|
|
||||||
PauseAnimation { duration: 100}
|
|
||||||
NumberAnimation { properties: "y"; duration: 100; easing.type: Easing.Linear }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
displaced: Transition {
|
|
||||||
NumberAnimation { properties: "y"; duration: 100; easing.type: Easing.Linear }
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // Rectangle trayWindowBackground
|
} // Rectangle trayWindowBackground
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,15 +104,20 @@ bool OCUpdater::performUpdate()
|
|||||||
QString updateFile = settings.value(updateAvailableC).toString();
|
QString updateFile = settings.value(updateAvailableC).toString();
|
||||||
if (!updateFile.isEmpty() && QFile(updateFile).exists()
|
if (!updateFile.isEmpty() && QFile(updateFile).exists()
|
||||||
&& !updateSucceeded() /* Someone might have run the updater manually between restarts */) {
|
&& !updateSucceeded() /* Someone might have run the updater manually between restarts */) {
|
||||||
const QString name = Theme::instance()->appNameGUI();
|
const auto messageBoxStartInstaller = new QMessageBox(QMessageBox::Information,
|
||||||
if (QMessageBox::information(nullptr, tr("New %1 Update Ready").arg(name),
|
tr("New %1 update ready").arg(Theme::instance()->appNameGUI()),
|
||||||
tr("A new update for %1 is about to be installed. The updater may ask\n"
|
tr("A new update for %1 is about to be installed. The updater may ask\n"
|
||||||
"for additional privileges during the process.")
|
"for additional privileges during the process.")
|
||||||
.arg(name),
|
.arg(Theme::instance()->appNameGUI()),
|
||||||
QMessageBox::Ok)) {
|
QMessageBox::Ok,
|
||||||
|
nullptr);
|
||||||
|
|
||||||
|
messageBoxStartInstaller->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
|
||||||
|
connect(messageBoxStartInstaller, &QMessageBox::finished, this, [this] {
|
||||||
slotStartInstaller();
|
slotStartInstaller();
|
||||||
return true;
|
});
|
||||||
}
|
messageBoxStartInstaller->open();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -216,6 +221,7 @@ void OCUpdater::slotStartInstaller()
|
|||||||
|
|
||||||
QProcess::startDetached("powershell.exe", QStringList{"-Command", command});
|
QProcess::startDetached("powershell.exe", QStringList{"-Command", command});
|
||||||
}
|
}
|
||||||
|
qApp->quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
void OCUpdater::checkForUpdate()
|
void OCUpdater::checkForUpdate()
|
||||||
@@ -476,7 +482,6 @@ void NSISUpdater::showUpdateErrorDialog(const QString &targetVersion)
|
|||||||
// askagain: do nothing
|
// askagain: do nothing
|
||||||
connect(retry, &QAbstractButton::clicked, this, [this]() {
|
connect(retry, &QAbstractButton::clicked, this, [this]() {
|
||||||
slotStartInstaller();
|
slotStartInstaller();
|
||||||
qApp->quit();
|
|
||||||
});
|
});
|
||||||
connect(getupdate, &QAbstractButton::clicked, this, [this]() {
|
connect(getupdate, &QAbstractButton::clicked, this, [this]() {
|
||||||
slotOpenUpdateUrl();
|
slotOpenUpdateUrl();
|
||||||
|
|||||||
@@ -16,12 +16,18 @@
|
|||||||
|
|
||||||
#include "common/utility.h"
|
#include "common/utility.h"
|
||||||
#include "account.h"
|
#include "account.h"
|
||||||
|
#include "creds/webflowcredentials.h"
|
||||||
|
#include "networkjobs.h"
|
||||||
#include "wizard/owncloudwizardcommon.h"
|
#include "wizard/owncloudwizardcommon.h"
|
||||||
#include "theme.h"
|
#include "theme.h"
|
||||||
#include "linklabel.h"
|
#include "linklabel.h"
|
||||||
|
|
||||||
#include "QProgressIndicator.h"
|
#include "QProgressIndicator.h"
|
||||||
|
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QStringLiteral>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
namespace OCC {
|
namespace OCC {
|
||||||
|
|
||||||
Q_LOGGING_CATEGORY(lcFlow2AuthWidget, "nextcloud.gui.wizard.flow2authwidget", QtInfoMsg)
|
Q_LOGGING_CATEGORY(lcFlow2AuthWidget, "nextcloud.gui.wizard.flow2authwidget", QtInfoMsg)
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ void OwncloudAdvancedSetupPage::initializePage()
|
|||||||
_ui.confCheckBoxExternal->setChecked(cfgFile.confirmExternalStorage());
|
_ui.confCheckBoxExternal->setChecked(cfgFile.confirmExternalStorage());
|
||||||
|
|
||||||
fetchUserAvatar();
|
fetchUserAvatar();
|
||||||
fetchUserData();
|
setUserInformation();
|
||||||
|
|
||||||
customizeStyle();
|
customizeStyle();
|
||||||
|
|
||||||
@@ -201,20 +201,9 @@ void OwncloudAdvancedSetupPage::fetchUserAvatar()
|
|||||||
avatarJob->start();
|
avatarJob->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void OwncloudAdvancedSetupPage::fetchUserData()
|
void OwncloudAdvancedSetupPage::setUserInformation()
|
||||||
{
|
{
|
||||||
const auto account = _ocWizard->account();
|
const auto account = _ocWizard->account();
|
||||||
|
|
||||||
// Fetch user data
|
|
||||||
const auto userJob = new JsonApiJob(account, QLatin1String("ocs/v1.php/cloud/user"), this);
|
|
||||||
userJob->setTimeout(20 * 1000);
|
|
||||||
connect(userJob, &JsonApiJob::jsonReceived, this, [this](const QJsonDocument &json) {
|
|
||||||
const auto objData = json.object().value("ocs").toObject().value("data").toObject();
|
|
||||||
const auto displayName = objData.value("display-name").toString();
|
|
||||||
_ui.userNameLabel->setText(displayName);
|
|
||||||
});
|
|
||||||
userJob->start();
|
|
||||||
|
|
||||||
const auto serverUrl = account->url().toString();
|
const auto serverUrl = account->url().toString();
|
||||||
setServerAddressLabelUrl(serverUrl);
|
setServerAddressLabelUrl(serverUrl);
|
||||||
const auto userName = account->davDisplayName();
|
const auto userName = account->davDisplayName();
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ private:
|
|||||||
void setResolutionGuiVisible(bool value);
|
void setResolutionGuiVisible(bool value);
|
||||||
void setupResoultionWidget();
|
void setupResoultionWidget();
|
||||||
void fetchUserAvatar();
|
void fetchUserAvatar();
|
||||||
void fetchUserData();
|
void setUserInformation();
|
||||||
|
|
||||||
// TODO: remove when UX decision is made
|
// TODO: remove when UX decision is made
|
||||||
void refreshVirtualFilesAvailibility(const QString &path);
|
void refreshVirtualFilesAvailibility(const QString &path);
|
||||||
|
|||||||
@@ -52,6 +52,11 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool certificateError(const QWebEngineCertificateError &certificateError) override;
|
bool certificateError(const QWebEngineCertificateError &certificateError) override;
|
||||||
|
|
||||||
|
bool acceptNavigationRequest(const QUrl &url, QWebEnginePage::NavigationType type, bool isMainFrame) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool _enforceHttps = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// We need a separate class here, since we cannot simply return the same WebEnginePage object
|
// We need a separate class here, since we cannot simply return the same WebEnginePage object
|
||||||
@@ -186,8 +191,10 @@ QWebEnginePage * WebEnginePage::createWindow(QWebEnginePage::WebWindowType type)
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebEnginePage::setUrl(const QUrl &url) {
|
void WebEnginePage::setUrl(const QUrl &url)
|
||||||
|
{
|
||||||
QWebEnginePage::setUrl(url);
|
QWebEnginePage::setUrl(url);
|
||||||
|
_enforceHttps = url.scheme() == QStringLiteral("https");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WebEnginePage::certificateError(const QWebEngineCertificateError &certificateError)
|
bool WebEnginePage::certificateError(const QWebEngineCertificateError &certificateError)
|
||||||
@@ -211,6 +218,18 @@ bool WebEnginePage::certificateError(const QWebEngineCertificateError &certifica
|
|||||||
return ret == QMessageBox::Yes;
|
return ret == QMessageBox::Yes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WebEnginePage::acceptNavigationRequest(const QUrl &url, QWebEnginePage::NavigationType type, bool isMainFrame)
|
||||||
|
{
|
||||||
|
Q_UNUSED(type);
|
||||||
|
Q_UNUSED(isMainFrame);
|
||||||
|
|
||||||
|
if (_enforceHttps && url.scheme() != QStringLiteral("https") && url.scheme() != QStringLiteral("nc")) {
|
||||||
|
QMessageBox::warning(nullptr, "Security warning", "Can not follow non https link on a https website. This might be a security issue. Please contact your administrator");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
ExternalWebEnginePage::ExternalWebEnginePage(QWebEngineProfile *profile, QObject* parent) : QWebEnginePage(profile, parent) {
|
ExternalWebEnginePage::ExternalWebEnginePage(QWebEngineProfile *profile, QObject* parent) : QWebEnginePage(profile, parent) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ const char app_password[] = "_app-password";
|
|||||||
Account::Account(QObject *parent)
|
Account::Account(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, _capabilities(QVariantMap())
|
, _capabilities(QVariantMap())
|
||||||
, _davPath(Theme::instance()->webDavPath())
|
|
||||||
{
|
{
|
||||||
qRegisterMetaType<AccountPtr>("AccountPtr");
|
qRegisterMetaType<AccountPtr>("AccountPtr");
|
||||||
qRegisterMetaType<Account *>("Account*");
|
qRegisterMetaType<Account *>("Account*");
|
||||||
@@ -85,18 +84,7 @@ Account::~Account() = default;
|
|||||||
|
|
||||||
QString Account::davPath() const
|
QString Account::davPath() const
|
||||||
{
|
{
|
||||||
if (capabilities().chunkingNg()) {
|
return davPathBase() + QLatin1Char('/') + davUser() + QLatin1Char('/');
|
||||||
// The chunking-ng means the server prefer to use the new webdav URL
|
|
||||||
return QLatin1String("/remote.php/dav/files/") + davUser() + QLatin1Char('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure to have a trailing slash
|
|
||||||
if (!_davPath.endsWith('/')) {
|
|
||||||
QString dp(_davPath);
|
|
||||||
dp.append('/');
|
|
||||||
return dp;
|
|
||||||
}
|
|
||||||
return _davPath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Account::setSharedThis(AccountPtr sharedThis)
|
void Account::setSharedThis(AccountPtr sharedThis)
|
||||||
@@ -104,6 +92,11 @@ void Account::setSharedThis(AccountPtr sharedThis)
|
|||||||
_sharedThis = sharedThis.toWeakRef();
|
_sharedThis = sharedThis.toWeakRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString Account::davPathBase()
|
||||||
|
{
|
||||||
|
return QStringLiteral("/remote.php/dav/files");
|
||||||
|
}
|
||||||
|
|
||||||
AccountPtr Account::sharedFromThis()
|
AccountPtr Account::sharedFromThis()
|
||||||
{
|
{
|
||||||
return _sharedThis.toStrongRef();
|
return _sharedThis.toStrongRef();
|
||||||
@@ -111,7 +104,7 @@ AccountPtr Account::sharedFromThis()
|
|||||||
|
|
||||||
QString Account::davUser() const
|
QString Account::davUser() const
|
||||||
{
|
{
|
||||||
return _davUser.isEmpty() ? _credentials->user() : _davUser;
|
return _davUser.isEmpty() && _credentials ? _credentials->user() : _davUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Account::setDavUser(const QString &newDavUser)
|
void Account::setDavUser(const QString &newDavUser)
|
||||||
@@ -498,7 +491,27 @@ void Account::slotHandleSslErrors(QNetworkReply *reply, QList<QSslError> errors)
|
|||||||
|
|
||||||
void Account::slotCredentialsFetched()
|
void Account::slotCredentialsFetched()
|
||||||
{
|
{
|
||||||
emit credentialsFetched(_credentials.data());
|
if (_davUser.isEmpty()) {
|
||||||
|
qCDebug(lcAccount) << "User id not set. Fetch it.";
|
||||||
|
const auto fetchUserNameJob = new JsonApiJob(sharedFromThis(), QStringLiteral("/ocs/v1.php/cloud/user"));
|
||||||
|
connect(fetchUserNameJob, &JsonApiJob::jsonReceived, this, [this, fetchUserNameJob](const QJsonDocument &json, int statusCode) {
|
||||||
|
fetchUserNameJob->deleteLater();
|
||||||
|
if (statusCode != 100) {
|
||||||
|
qCWarning(lcAccount) << "Could not fetch user id. Login will probably not work.";
|
||||||
|
emit credentialsFetched(_credentials.data());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto objData = json.object().value("ocs").toObject().value("data").toObject();
|
||||||
|
const auto userId = objData.value("id").toString("");
|
||||||
|
setDavUser(userId);
|
||||||
|
emit credentialsFetched(_credentials.data());
|
||||||
|
});
|
||||||
|
fetchUserNameJob->start();
|
||||||
|
} else {
|
||||||
|
qCDebug(lcAccount) << "User id already fetched.";
|
||||||
|
emit credentialsFetched(_credentials.data());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Account::slotCredentialsAsked()
|
void Account::slotCredentialsAsked()
|
||||||
@@ -571,15 +584,6 @@ void Account::setServerVersion(const QString &version)
|
|||||||
emit serverVersionChanged(this, oldServerVersion, version);
|
emit serverVersionChanged(this, oldServerVersion, version);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Account::setNonShib(bool nonShib)
|
|
||||||
{
|
|
||||||
if (nonShib) {
|
|
||||||
_davPath = Theme::instance()->webDavPathNonShib();
|
|
||||||
} else {
|
|
||||||
_davPath = Theme::instance()->webDavPath();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Account::writeAppPasswordOnce(QString appPassword){
|
void Account::writeAppPasswordOnce(QString appPassword){
|
||||||
if(_wroteAppPassword)
|
if(_wroteAppPassword)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -124,8 +124,6 @@ public:
|
|||||||
* @returns the (themeable) dav path for the account.
|
* @returns the (themeable) dav path for the account.
|
||||||
*/
|
*/
|
||||||
QString davPath() const;
|
QString davPath() const;
|
||||||
void setDavPath(const QString &s) { _davPath = s; }
|
|
||||||
void setNonShib(bool nonShib);
|
|
||||||
|
|
||||||
/** Returns webdav entry URL, based on url() */
|
/** Returns webdav entry URL, based on url() */
|
||||||
QUrl davUrl() const;
|
QUrl davUrl() const;
|
||||||
@@ -296,6 +294,8 @@ private:
|
|||||||
Account(QObject *parent = nullptr);
|
Account(QObject *parent = nullptr);
|
||||||
void setSharedThis(AccountPtr sharedThis);
|
void setSharedThis(AccountPtr sharedThis);
|
||||||
|
|
||||||
|
static QString davPathBase();
|
||||||
|
|
||||||
QWeakPointer<Account> _sharedThis;
|
QWeakPointer<Account> _sharedThis;
|
||||||
QString _id;
|
QString _id;
|
||||||
QString _davUser;
|
QString _davUser;
|
||||||
@@ -329,7 +329,6 @@ private:
|
|||||||
|
|
||||||
static QString _configFileName;
|
static QString _configFileName;
|
||||||
|
|
||||||
QString _davPath; // defaults to value from theme, might be overwritten in brandings
|
|
||||||
ClientSideEncryption _e2e;
|
ClientSideEncryption _e2e;
|
||||||
|
|
||||||
/// Used in RemoteWipe
|
/// Used in RemoteWipe
|
||||||
|
|||||||
@@ -1663,7 +1663,6 @@ bool EncryptionHelper::fileEncryption(const QByteArray &key, const QByteArray &i
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
qCDebug(lcCse) << "Encrypting " << data;
|
|
||||||
if(!EVP_EncryptUpdate(ctx, unsignedData(out), &len, (unsigned char *)data.constData(), data.size())) {
|
if(!EVP_EncryptUpdate(ctx, unsignedData(out), &len, (unsigned char *)data.constData(), data.size())) {
|
||||||
qCInfo(lcCse()) << "Could not encrypt";
|
qCInfo(lcCse()) << "Could not encrypt";
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -50,7 +50,6 @@
|
|||||||
#define DEFAULT_MAX_LOG_LINES 20000
|
#define DEFAULT_MAX_LOG_LINES 20000
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
static constexpr char allowChecksumValidationFailC[] = "allowChecksumValidationFail";
|
|
||||||
static constexpr char showMainDialogAsNormalWindowC[] = "showMainDialogAsNormalWindow";
|
static constexpr char showMainDialogAsNormalWindowC[] = "showMainDialogAsNormalWindow";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -892,11 +891,6 @@ void ConfigFile::setMoveToTrash(bool isChecked)
|
|||||||
setValue(moveToTrashC, isChecked);
|
setValue(moveToTrashC, isChecked);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ConfigFile::allowChecksumValidationFail() const
|
|
||||||
{
|
|
||||||
return getValue(allowChecksumValidationFailC, {}, false).toBool();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ConfigFile::showMainDialogAsNormalWindow() const {
|
bool ConfigFile::showMainDialogAsNormalWindow() const {
|
||||||
return getValue(showMainDialogAsNormalWindowC, {}, false).toBool();
|
return getValue(showMainDialogAsNormalWindowC, {}, false).toBool();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,9 +145,6 @@ public:
|
|||||||
bool moveToTrash() const;
|
bool moveToTrash() const;
|
||||||
void setMoveToTrash(bool);
|
void setMoveToTrash(bool);
|
||||||
|
|
||||||
/** should we allow checksum validation to fail? set to true to workaround corrupted checksums **/
|
|
||||||
bool allowChecksumValidationFail() const;
|
|
||||||
|
|
||||||
bool showMainDialogAsNormalWindow() const;
|
bool showMainDialogAsNormalWindow() const;
|
||||||
|
|
||||||
static bool setConfDir(const QString &value);
|
static bool setConfDir(const QString &value);
|
||||||
|
|||||||
@@ -403,7 +403,7 @@ bool LsColJob::finished()
|
|||||||
connect(&parser, &LsColXMLParser::finishedWithoutError,
|
connect(&parser, &LsColXMLParser::finishedWithoutError,
|
||||||
this, &LsColJob::finishedWithoutError);
|
this, &LsColJob::finishedWithoutError);
|
||||||
|
|
||||||
QString expectedPath = reply()->request().url().path(); // something like "/owncloud/remote.php/webdav/folder"
|
QString expectedPath = reply()->request().url().path(); // something like "/owncloud/remote.php/dav/folder"
|
||||||
if (!parser.parse(reply()->readAll(), &_folderInfos, expectedPath)) {
|
if (!parser.parse(reply()->readAll(), &_folderInfos, expectedPath)) {
|
||||||
// XML parse error
|
// XML parse error
|
||||||
emit finishedWithError(reply());
|
emit finishedWithError(reply());
|
||||||
@@ -926,7 +926,10 @@ void DetermineAuthTypeJob::start()
|
|||||||
oldFlowRequired->setIgnoreCredentialFailure(true);
|
oldFlowRequired->setIgnoreCredentialFailure(true);
|
||||||
|
|
||||||
connect(get, &SimpleNetworkJob::finishedSignal, this, [this, get]() {
|
connect(get, &SimpleNetworkJob::finishedSignal, this, [this, get]() {
|
||||||
if (get->reply()->error() == QNetworkReply::AuthenticationRequiredError) {
|
const auto reply = get->reply();
|
||||||
|
const auto wwwAuthenticateHeader = reply->rawHeader("WWW-Authenticate");
|
||||||
|
if (reply->error() == QNetworkReply::AuthenticationRequiredError
|
||||||
|
&& (wwwAuthenticateHeader.startsWith("Basic") || wwwAuthenticateHeader.startsWith("Bearer"))) {
|
||||||
_resultGet = Basic;
|
_resultGet = Basic;
|
||||||
} else {
|
} else {
|
||||||
_resultGet = LoginFlowV2;
|
_resultGet = LoginFlowV2;
|
||||||
|
|||||||
@@ -28,8 +28,6 @@
|
|||||||
#include "propagatedownloadencrypted.h"
|
#include "propagatedownloadencrypted.h"
|
||||||
#include "common/vfs.h"
|
#include "common/vfs.h"
|
||||||
|
|
||||||
#include "configfile.h"
|
|
||||||
|
|
||||||
#include <QLoggingCategory>
|
#include <QLoggingCategory>
|
||||||
#include <QNetworkAccessManager>
|
#include <QNetworkAccessManager>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
@@ -40,11 +38,6 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace {
|
|
||||||
constexpr quint16 numChecksumFailuresAllowed = 1;
|
|
||||||
constexpr char *checksumFailureDbRecordPrefix = "ChecksumValidationFailed_";
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace OCC {
|
namespace OCC {
|
||||||
|
|
||||||
Q_LOGGING_CATEGORY(lcGetJob, "nextcloud.sync.networkjob.get", QtInfoMsg)
|
Q_LOGGING_CATEGORY(lcGetJob, "nextcloud.sync.networkjob.get", QtInfoMsg)
|
||||||
@@ -830,38 +823,8 @@ void PropagateDownloadFile::slotGetFinished()
|
|||||||
validator->start(_tmpFile.fileName(), checksumHeader);
|
validator->start(_tmpFile.fileName(), checksumHeader);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PropagateDownloadFile::slotChecksumFail(const QString &errMsg, const QByteArray &checksumType, const QByteArray &checksum, const QString &filePath)
|
void PropagateDownloadFile::slotChecksumFail(const QString &errMsg)
|
||||||
{
|
{
|
||||||
if (!checksumType.isEmpty() && !checksum.isEmpty() && !filePath.isEmpty()) {
|
|
||||||
ConfigFile cfgFile;
|
|
||||||
|
|
||||||
if (cfgFile.allowChecksumValidationFail()) {
|
|
||||||
const auto key = QString(checksumFailureDbRecordPrefix + _item->_fileId);
|
|
||||||
const QStringList mismatchEntryForFileSplitted = propagator()->_journal->keyValueStoreGet(key).toString().split(":", QString::SkipEmptyParts);
|
|
||||||
const QByteArray mismatchChecksumForFile = mismatchEntryForFileSplitted.size() > 0 ? mismatchEntryForFileSplitted[0].toUtf8() : QByteArray();
|
|
||||||
const auto numChecksumMismatchCases = mismatchEntryForFileSplitted.size() > 1 ? mismatchEntryForFileSplitted[1].toInt() : 0;
|
|
||||||
|
|
||||||
// format must be CHECKSUM:COUNT
|
|
||||||
Q_ASSERT(mismatchEntryForFileSplitted.size() != 1);
|
|
||||||
if (mismatchEntryForFileSplitted.size() == 1) {
|
|
||||||
qCCritical(lcPropagateDownload) << "mismatchEntryForFile has incorrect format. Should be CHECKSUM:COUNT";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (numChecksumMismatchCases < numChecksumFailuresAllowed || mismatchChecksumForFile != checksum) {
|
|
||||||
// not enough failures or different checksum this time
|
|
||||||
qCInfo(lcPropagateDownload) << "Checksum validation has failed" << numChecksumMismatchCases << " times, with previous checksum<" << mismatchChecksumForFile << "> and, current checksum<" << checksum << ">, but, allowChecksumValidationFail is set.Let's give it another try...";
|
|
||||||
const auto numCasesToSet = mismatchChecksumForFile != checksum ? 1 : numChecksumMismatchCases + 1;
|
|
||||||
const QString value = QString::fromUtf8(checksum) + QStringLiteral(":") + QString::number(numCasesToSet);
|
|
||||||
propagator()->_journal->keyValueStoreSet(key, value);
|
|
||||||
} else {
|
|
||||||
propagator()->_journal->keyValueStoreDelete(key);
|
|
||||||
qCInfo(lcPropagateDownload) << "Checksum validation has failed" << numChecksumMismatchCases << " times, but, allowChecksumValidationFail is set, so, let's continue...";
|
|
||||||
startContentChecksumCompute(checksumType, filePath);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileSystem::remove(_tmpFile.fileName());
|
FileSystem::remove(_tmpFile.fileName());
|
||||||
propagator()->_anotherSyncNeeded = true;
|
propagator()->_anotherSyncNeeded = true;
|
||||||
done(SyncFileItem::SoftError, errMsg); // tr("The file downloaded with a broken checksum, will be redownloaded."));
|
done(SyncFileItem::SoftError, errMsg); // tr("The file downloaded with a broken checksum, will be redownloaded."));
|
||||||
@@ -889,18 +852,6 @@ void PropagateDownloadFile::deleteExistingFolder()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PropagateDownloadFile::startContentChecksumCompute(const QByteArray &checksumType, const QString &path)
|
|
||||||
{
|
|
||||||
qCInfo(lcPropagateDownload) << "Start checksum compute with checksumType:" << checksumType << " for path:" << path;
|
|
||||||
// Compute the content checksum.
|
|
||||||
const auto computeChecksum = new ComputeChecksum(this);
|
|
||||||
computeChecksum->setChecksumType(checksumType);
|
|
||||||
|
|
||||||
connect(computeChecksum, &ComputeChecksum::done,
|
|
||||||
this, &PropagateDownloadFile::contentChecksumComputed);
|
|
||||||
computeChecksum->start(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace { // Anonymous namespace for the recall feature
|
namespace { // Anonymous namespace for the recall feature
|
||||||
static QString makeRecallFileName(const QString &fn)
|
static QString makeRecallFileName(const QString &fn)
|
||||||
{
|
{
|
||||||
@@ -989,9 +940,13 @@ void PropagateDownloadFile::transmissionChecksumValidated(const QByteArray &chec
|
|||||||
return contentChecksumComputed(checksumType, checksum);
|
return contentChecksumComputed(checksumType, checksum);
|
||||||
}
|
}
|
||||||
|
|
||||||
startContentChecksumCompute(theContentChecksumType, _tmpFile.fileName());
|
// Compute the content checksum.
|
||||||
|
auto computeChecksum = new ComputeChecksum(this);
|
||||||
|
computeChecksum->setChecksumType(theContentChecksumType);
|
||||||
|
|
||||||
propagator()->_journal->keyValueStoreDelete(QString(checksumFailureDbRecordPrefix + _item->_fileId));
|
connect(computeChecksum, &ComputeChecksum::done,
|
||||||
|
this, &PropagateDownloadFile::contentChecksumComputed);
|
||||||
|
computeChecksum->start(_tmpFile.fileName());
|
||||||
}
|
}
|
||||||
|
|
||||||
void PropagateDownloadFile::contentChecksumComputed(const QByteArray &checksumType, const QByteArray &checksum)
|
void PropagateDownloadFile::contentChecksumComputed(const QByteArray &checksumType, const QByteArray &checksum)
|
||||||
|
|||||||
@@ -202,14 +202,12 @@ private slots:
|
|||||||
|
|
||||||
void abort(PropagatorJob::AbortType abortType) override;
|
void abort(PropagatorJob::AbortType abortType) override;
|
||||||
void slotDownloadProgress(qint64, qint64);
|
void slotDownloadProgress(qint64, qint64);
|
||||||
void slotChecksumFail(const QString &errMsg, const QByteArray &checksumType, const QByteArray &checksum, const QString &filePath);
|
void slotChecksumFail(const QString &errMsg);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void startAfterIsEncryptedIsChecked();
|
void startAfterIsEncryptedIsChecked();
|
||||||
void deleteExistingFolder();
|
void deleteExistingFolder();
|
||||||
|
|
||||||
void startContentChecksumCompute(const QByteArray &checksumType, const QString &path);
|
|
||||||
|
|
||||||
qint64 _resumeStart;
|
qint64 _resumeStart;
|
||||||
qint64 _downloadProgress;
|
qint64 _downloadProgress;
|
||||||
QPointer<GETFileJob> _job;
|
QPointer<GETFileJob> _job;
|
||||||
|
|||||||
@@ -76,6 +76,9 @@ void PushNotifications::closeWebSocket()
|
|||||||
_reconnectTimer->stop();
|
_reconnectTimer->stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
disconnect(_webSocket, QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error), this, &PushNotifications::onWebSocketError);
|
||||||
|
disconnect(_webSocket, &QWebSocket::sslErrors, this, &PushNotifications::onWebSocketSslErrors);
|
||||||
|
|
||||||
_webSocket->close();
|
_webSocket->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,6 +174,8 @@ void PushNotifications::openWebSocket()
|
|||||||
const auto webSocketUrl = capabilities.pushNotificationsWebSocketUrl();
|
const auto webSocketUrl = capabilities.pushNotificationsWebSocketUrl();
|
||||||
|
|
||||||
qCInfo(lcPushNotifications) << "Open connection to websocket on" << webSocketUrl << "for account" << _account->url();
|
qCInfo(lcPushNotifications) << "Open connection to websocket on" << webSocketUrl << "for account" << _account->url();
|
||||||
|
connect(_webSocket, QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error), this, &PushNotifications::onWebSocketError);
|
||||||
|
connect(_webSocket, &QWebSocket::sslErrors, this, &PushNotifications::onWebSocketSslErrors);
|
||||||
_webSocket->open(webSocketUrl);
|
_webSocket->open(webSocketUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -799,55 +799,8 @@ void SyncEngine::slotPropagationFinished(bool success)
|
|||||||
_anotherSyncNeeded = ImmediateFollowUp;
|
_anotherSyncNeeded = ImmediateFollowUp;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove this when the file restoration problem is fixed for a user
|
|
||||||
bool shouldStartSyncAgain = false;
|
|
||||||
const auto checkAndOverrideSetDataFingerprint = [this, &shouldStartSyncAgain] {
|
|
||||||
const int dataFingerprintOverrideThreshold = 9;
|
|
||||||
const QString dataFingerprintOverrideHostHash = QStringLiteral("63debc9ef6d217649ea70632ca573a1db7a237ba61c48cdd2bf797f7060233db");
|
|
||||||
const auto accountHost = account()->url().host();
|
|
||||||
const auto accountDisplayName = account()->displayName();
|
|
||||||
|
|
||||||
if (_dataFingerprintSetFailCount >= 0) {
|
|
||||||
qCWarning(lcEngine) << "setDataFingerprint has failed for account" << accountDisplayName << "on host" << accountHost << "due to sync errors. Checking the possibility for override...";
|
|
||||||
|
|
||||||
if (_dataFingerprintSetFailCount > 0) {
|
|
||||||
if (_dataFingerprintSetFailCount >= dataFingerprintOverrideThreshold) {
|
|
||||||
qCWarning(lcEngine) << "All sync attempts failed for account" << accountDisplayName << "on host" << accountHost << "setting the dataFingerprint anyway.";
|
|
||||||
_journal->setDataFingerprint(_discoveryPhase->_dataFingerprint);
|
|
||||||
// this mechanism should only run once per app launch
|
|
||||||
_dataFingerprintSetFailCount = -1;
|
|
||||||
} else {
|
|
||||||
++_dataFingerprintSetFailCount;
|
|
||||||
// request to start sync again as it won't happen by itself unless the file has changed on the server or in the local folder
|
|
||||||
shouldStartSyncAgain = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// only compare hash once
|
|
||||||
// if it matches - we don't need to calculate it again while _dataFingerprintSetFailCount is greater than 0
|
|
||||||
const auto accountHostHash = QString::fromUtf8(QCryptographicHash::hash(accountHost.toUtf8(), QCryptographicHash::Sha256).toHex());
|
|
||||||
|
|
||||||
if (accountHostHash == dataFingerprintOverrideHostHash) {
|
|
||||||
qCInfo(lcEngine) << "accountHostHash" << accountHostHash << "equals to dataFingerprintOverrideHostHash" << dataFingerprintOverrideHostHash << "_dataFingerprintSetFailCount" << _dataFingerprintSetFailCount;
|
|
||||||
++_dataFingerprintSetFailCount;
|
|
||||||
// request to start sync again as it won't happen by itself unless the file has changed on the server or in the local folder
|
|
||||||
shouldStartSyncAgain = true;
|
|
||||||
} else {
|
|
||||||
qCInfo(lcEngine) << "accountHostHash" << accountHostHash << "differs from dataFingerprintOverrideHostHash" << dataFingerprintOverrideHostHash;
|
|
||||||
// give up on calculating the has next time, as it's not the host we are looking for
|
|
||||||
_dataFingerprintSetFailCount = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qCWarning(lcEngine) << "setDataFingerprint was overridden already for account" << accountDisplayName << "on host" << accountHost << "but is failing again! Or, it's not the host that we are looking for.";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
//
|
|
||||||
|
|
||||||
if (success && _discoveryPhase) {
|
if (success && _discoveryPhase) {
|
||||||
_journal->setDataFingerprint(_discoveryPhase->_dataFingerprint);
|
_journal->setDataFingerprint(_discoveryPhase->_dataFingerprint);
|
||||||
} else if (_discoveryPhase) {
|
|
||||||
// TODO: Remove this when the file restoration problem is fixed for a user
|
|
||||||
checkAndOverrideSetDataFingerprint();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
conflictRecordMaintenance();
|
conflictRecordMaintenance();
|
||||||
@@ -863,12 +816,6 @@ void SyncEngine::slotPropagationFinished(bool success)
|
|||||||
emit transmissionProgress(*_progressInfo);
|
emit transmissionProgress(*_progressInfo);
|
||||||
|
|
||||||
finalize(success);
|
finalize(success);
|
||||||
|
|
||||||
if (shouldStartSyncAgain) {
|
|
||||||
// TODO: Remove this when the file restoration problem is fixed for a user
|
|
||||||
qCWarning(lcEngine) << "Starting sync again for account" << account()->displayName() << "on host" << account()->url().host() << "due to setDataFingerprint override is running.";
|
|
||||||
startSync();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SyncEngine::finalize(bool success)
|
void SyncEngine::finalize(bool success)
|
||||||
|
|||||||
@@ -291,9 +291,6 @@ private:
|
|||||||
LocalDiscoveryStyle _lastLocalDiscoveryStyle = LocalDiscoveryStyle::FilesystemOnly;
|
LocalDiscoveryStyle _lastLocalDiscoveryStyle = LocalDiscoveryStyle::FilesystemOnly;
|
||||||
LocalDiscoveryStyle _localDiscoveryStyle = LocalDiscoveryStyle::FilesystemOnly;
|
LocalDiscoveryStyle _localDiscoveryStyle = LocalDiscoveryStyle::FilesystemOnly;
|
||||||
std::set<QString> _localDiscoveryPaths;
|
std::set<QString> _localDiscoveryPaths;
|
||||||
|
|
||||||
// TODO: Remove this when the file restoration problem is fixed for a user
|
|
||||||
int _dataFingerprintSetFailCount = 0;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -643,16 +643,6 @@ bool Theme::wizardSelectiveSyncDefaultNothing() const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Theme::webDavPath() const
|
|
||||||
{
|
|
||||||
return QLatin1String("remote.php/webdav/");
|
|
||||||
}
|
|
||||||
|
|
||||||
QString Theme::webDavPathNonShib() const
|
|
||||||
{
|
|
||||||
return QLatin1String("remote.php/nonshib-webdav/");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Theme::linkSharing() const
|
bool Theme::linkSharing() const
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -337,15 +337,6 @@ public:
|
|||||||
*/
|
*/
|
||||||
virtual bool wizardHideExternalStorageConfirmationCheckbox() const;
|
virtual bool wizardHideExternalStorageConfirmationCheckbox() const;
|
||||||
|
|
||||||
/**
|
|
||||||
* Alternative path on the server that provides access to the webdav capabilities
|
|
||||||
*
|
|
||||||
* Attention: Make sure that this string does NOT have a leading slash and that
|
|
||||||
* it has a trailing slash, for example "remote.php/webdav/".
|
|
||||||
*/
|
|
||||||
virtual QString webDavPath() const;
|
|
||||||
virtual QString webDavPathNonShib() const;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Sharing options
|
* @brief Sharing options
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ endif()
|
|||||||
|
|
||||||
nextcloud_add_benchmark(LargeSync)
|
nextcloud_add_benchmark(LargeSync)
|
||||||
|
|
||||||
|
nextcloud_add_test(Account)
|
||||||
nextcloud_add_test(FolderMan)
|
nextcloud_add_test(FolderMan)
|
||||||
nextcloud_add_test(RemoteWipe)
|
nextcloud_add_test(RemoteWipe)
|
||||||
|
|
||||||
|
|||||||
@@ -34,18 +34,19 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
static const QUrl sRootUrl("owncloud://somehost/owncloud/remote.php/webdav/");
|
static const QUrl sRootUrl("owncloud://somehost/owncloud/remote.php/dav/");
|
||||||
static const QUrl sRootUrl2("owncloud://somehost/owncloud/remote.php/dav/files/admin/");
|
static const QUrl sRootUrl2("owncloud://somehost/owncloud/remote.php/dav/files/admin/");
|
||||||
static const QUrl sUploadUrl("owncloud://somehost/owncloud/remote.php/dav/uploads/admin/");
|
static const QUrl sUploadUrl("owncloud://somehost/owncloud/remote.php/dav/uploads/admin/");
|
||||||
|
|
||||||
inline QString getFilePathFromUrl(const QUrl &url) {
|
inline QString getFilePathFromUrl(const QUrl &url)
|
||||||
|
{
|
||||||
QString path = url.path();
|
QString path = url.path();
|
||||||
if (path.startsWith(sRootUrl.path()))
|
|
||||||
return path.mid(sRootUrl.path().length());
|
|
||||||
if (path.startsWith(sRootUrl2.path()))
|
if (path.startsWith(sRootUrl2.path()))
|
||||||
return path.mid(sRootUrl2.path().length());
|
return path.mid(sRootUrl2.path().length());
|
||||||
if (path.startsWith(sUploadUrl.path()))
|
if (path.startsWith(sUploadUrl.path()))
|
||||||
return path.mid(sUploadUrl.path().length());
|
return path.mid(sUploadUrl.path().length());
|
||||||
|
if (path.startsWith(sRootUrl.path()))
|
||||||
|
return path.mid(sRootUrl.path().length());
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
34
test/testaccount.cpp
Normal file
34
test/testaccount.cpp
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* This software is in the public domain, furnished "as is", without technical
|
||||||
|
* support, and with no warranty, express or implied, as to its usefulness for
|
||||||
|
* any purpose.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <qglobal.h>
|
||||||
|
#include <QTemporaryDir>
|
||||||
|
#include <QtTest>
|
||||||
|
|
||||||
|
#include "common/utility.h"
|
||||||
|
#include "folderman.h"
|
||||||
|
#include "account.h"
|
||||||
|
#include "accountstate.h"
|
||||||
|
#include "configfile.h"
|
||||||
|
#include "testhelper.h"
|
||||||
|
|
||||||
|
using namespace OCC;
|
||||||
|
|
||||||
|
class TestAccount: public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void testAccountDavPath_unitialized_noCrash()
|
||||||
|
{
|
||||||
|
AccountPtr account = Account::create();
|
||||||
|
account->davPath();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
QTEST_APPLESS_MAIN(TestAccount)
|
||||||
|
#include "testaccount.moc"
|
||||||
@@ -214,10 +214,11 @@ private slots:
|
|||||||
if (verb == "PROPFIND") {
|
if (verb == "PROPFIND") {
|
||||||
auto data = stream->readAll();
|
auto data = stream->readAll();
|
||||||
if (data.contains("data-fingerprint")) {
|
if (data.contains("data-fingerprint")) {
|
||||||
if (request.url().path().endsWith("webdav/"))
|
if (request.url().path().endsWith("dav/files/admin/")) {
|
||||||
++fingerprintRequests;
|
++fingerprintRequests;
|
||||||
else
|
} else {
|
||||||
fingerprintRequests = -10000; // fingerprint queried on incorrect path
|
fingerprintRequests = -10000; // fingerprint queried on incorrect path
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ using namespace OCC::Utility;
|
|||||||
_successDown = true;
|
_successDown = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void slotDownError(const QString &errMsg, const QByteArray&, const QByteArray&, const QString&) {
|
void slotDownError(const QString &errMsg)
|
||||||
|
{
|
||||||
QCOMPARE(_expectedError, errMsg);
|
QCOMPARE(_expectedError, errMsg);
|
||||||
_errorSeen = true;
|
_errorSeen = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,12 @@ private slots:
|
|||||||
QVERIFY(folderman->addFolder(newAccountState.data(), folderDefinition(dirPath + "/sub/ownCloud1")));
|
QVERIFY(folderman->addFolder(newAccountState.data(), folderDefinition(dirPath + "/sub/ownCloud1")));
|
||||||
QVERIFY(folderman->addFolder(newAccountState.data(), folderDefinition(dirPath + "/ownCloud2")));
|
QVERIFY(folderman->addFolder(newAccountState.data(), folderDefinition(dirPath + "/ownCloud2")));
|
||||||
|
|
||||||
|
const auto folderList = folderman->map();
|
||||||
|
|
||||||
|
for (const auto &folder : folderList) {
|
||||||
|
QVERIFY(!folder->isSyncRunning());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// those should be allowed
|
// those should be allowed
|
||||||
// QString FolderMan::checkPathValidityForNewFolder(const QString& path, const QUrl &serverUrl, bool forNewDirectory)
|
// QString FolderMan::checkPathValidityForNewFolder(const QString& path, const QUrl &serverUrl, bool forNewDirectory)
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ private slots:
|
|||||||
auto oldLocalState = fakeFolder.currentLocalState();
|
auto oldLocalState = fakeFolder.currentLocalState();
|
||||||
auto oldRemoteState = fakeFolder.currentRemoteState();
|
auto oldRemoteState = fakeFolder.currentRemoteState();
|
||||||
|
|
||||||
QString errorFolder = "webdav/B";
|
QString errorFolder = "dav/files/admin/B";
|
||||||
QString fatalErrorPrefix = "Server replied with an error while reading directory 'B' : ";
|
QString fatalErrorPrefix = "Server replied with an error while reading directory 'B' : ";
|
||||||
fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *)
|
fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *)
|
||||||
-> QNetworkReply *{
|
-> QNetworkReply *{
|
||||||
@@ -133,7 +133,7 @@ private slots:
|
|||||||
//
|
//
|
||||||
// Check the same discovery error on the sync root
|
// Check the same discovery error on the sync root
|
||||||
//
|
//
|
||||||
errorFolder = "webdav/";
|
errorFolder = "dav/files/admin/";
|
||||||
fatalErrorPrefix = "Server replied with an error while reading directory '' : ";
|
fatalErrorPrefix = "Server replied with an error while reading directory '' : ";
|
||||||
errorSpy.clear();
|
errorSpy.clear();
|
||||||
QVERIFY(!fakeFolder.syncOnce());
|
QVERIFY(!fakeFolder.syncOnce());
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ private slots:
|
|||||||
const QByteArray testXml = "<?xml version='1.0' encoding='utf-8'?>"
|
const QByteArray testXml = "<?xml version='1.0' encoding='utf-8'?>"
|
||||||
"<d:multistatus xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\">"
|
"<d:multistatus xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\">"
|
||||||
"<d:response>"
|
"<d:response>"
|
||||||
"<d:href>/oc/remote.php/webdav/sharefolder/</d:href>"
|
"<d:href>/oc/remote.php/dav/sharefolder/</d:href>"
|
||||||
"<d:propstat>"
|
"<d:propstat>"
|
||||||
"<d:prop>"
|
"<d:prop>"
|
||||||
"<oc:id>00004213ocobzus5kn6s</oc:id>"
|
"<oc:id>00004213ocobzus5kn6s</oc:id>"
|
||||||
@@ -77,7 +77,7 @@ private slots:
|
|||||||
"</d:propstat>"
|
"</d:propstat>"
|
||||||
"</d:response>"
|
"</d:response>"
|
||||||
"<d:response>"
|
"<d:response>"
|
||||||
"<d:href>/oc/remote.php/webdav/sharefolder/quitte.pdf</d:href>"
|
"<d:href>/oc/remote.php/dav/sharefolder/quitte.pdf</d:href>"
|
||||||
"<d:propstat>"
|
"<d:propstat>"
|
||||||
"<d:prop>"
|
"<d:prop>"
|
||||||
"<oc:id>00004215ocobzus5kn6s</oc:id>"
|
"<oc:id>00004215ocobzus5kn6s</oc:id>"
|
||||||
@@ -110,16 +110,16 @@ private slots:
|
|||||||
this, SLOT(slotFinishedSuccessfully()) );
|
this, SLOT(slotFinishedSuccessfully()) );
|
||||||
|
|
||||||
QHash <QString, ExtraFolderInfo> sizes;
|
QHash <QString, ExtraFolderInfo> sizes;
|
||||||
QVERIFY(parser.parse( testXml, &sizes, "/oc/remote.php/webdav/sharefolder" ));
|
QVERIFY(parser.parse( testXml, &sizes, "/oc/remote.php/dav/sharefolder" ));
|
||||||
|
|
||||||
QVERIFY(_success);
|
QVERIFY(_success);
|
||||||
QCOMPARE(sizes.size(), 1 ); // Quota info in the XML
|
QCOMPARE(sizes.size(), 1 ); // Quota info in the XML
|
||||||
|
|
||||||
QVERIFY(_items.contains("/oc/remote.php/webdav/sharefolder/quitte.pdf"));
|
QVERIFY(_items.contains("/oc/remote.php/dav/sharefolder/quitte.pdf"));
|
||||||
QVERIFY(_items.contains("/oc/remote.php/webdav/sharefolder"));
|
QVERIFY(_items.contains("/oc/remote.php/dav/sharefolder"));
|
||||||
QVERIFY(_items.size() == 2 );
|
QVERIFY(_items.size() == 2 );
|
||||||
|
|
||||||
QVERIFY(_subdirs.contains("/oc/remote.php/webdav/sharefolder/"));
|
QVERIFY(_subdirs.contains("/oc/remote.php/dav/sharefolder/"));
|
||||||
QVERIFY(_subdirs.size() == 1);
|
QVERIFY(_subdirs.size() == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +127,7 @@ private slots:
|
|||||||
const QByteArray testXml = "X<?xml version='1.0' encoding='utf-8'?>"
|
const QByteArray testXml = "X<?xml version='1.0' encoding='utf-8'?>"
|
||||||
"<d:multistatus xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\">"
|
"<d:multistatus xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\">"
|
||||||
"<d:response>"
|
"<d:response>"
|
||||||
"<d:href>/oc/remote.php/webdav/sharefolder/</d:href>"
|
"<d:href>/oc/remote.php/dav/sharefolder/</d:href>"
|
||||||
"<d:propstat>"
|
"<d:propstat>"
|
||||||
"<d:prop>"
|
"<d:prop>"
|
||||||
"<oc:id>00004213ocobzus5kn6s</oc:id>"
|
"<oc:id>00004213ocobzus5kn6s</oc:id>"
|
||||||
@@ -151,7 +151,7 @@ private slots:
|
|||||||
"</d:propstat>"
|
"</d:propstat>"
|
||||||
"</d:response>"
|
"</d:response>"
|
||||||
"<d:response>"
|
"<d:response>"
|
||||||
"<d:href>/oc/remote.php/webdav/sharefolder/quitte.pdf</d:href>"
|
"<d:href>/oc/remote.php/dav/sharefolder/quitte.pdf</d:href>"
|
||||||
"<d:propstat>"
|
"<d:propstat>"
|
||||||
"<d:prop>"
|
"<d:prop>"
|
||||||
"<oc:id>00004215ocobzus5kn6s</oc:id>"
|
"<oc:id>00004215ocobzus5kn6s</oc:id>"
|
||||||
@@ -184,7 +184,7 @@ private slots:
|
|||||||
this, SLOT(slotFinishedSuccessfully()) );
|
this, SLOT(slotFinishedSuccessfully()) );
|
||||||
|
|
||||||
QHash <QString, ExtraFolderInfo> sizes;
|
QHash <QString, ExtraFolderInfo> sizes;
|
||||||
QVERIFY(false == parser.parse( testXml, &sizes, "/oc/remote.php/webdav/sharefolder" )); // verify false
|
QVERIFY(false == parser.parse( testXml, &sizes, "/oc/remote.php/dav/sharefolder" )); // verify false
|
||||||
|
|
||||||
QVERIFY(!_success);
|
QVERIFY(!_success);
|
||||||
QVERIFY(sizes.size() == 0 ); // No quota info in the XML
|
QVERIFY(sizes.size() == 0 ); // No quota info in the XML
|
||||||
@@ -207,7 +207,7 @@ private slots:
|
|||||||
this, SLOT(slotFinishedSuccessfully()) );
|
this, SLOT(slotFinishedSuccessfully()) );
|
||||||
|
|
||||||
QHash <QString, ExtraFolderInfo> sizes;
|
QHash <QString, ExtraFolderInfo> sizes;
|
||||||
QVERIFY(false == parser.parse( testXml, &sizes, "/oc/remote.php/webdav/sharefolder" )); // verify false
|
QVERIFY(false == parser.parse( testXml, &sizes, "/oc/remote.php/dav/sharefolder" )); // verify false
|
||||||
|
|
||||||
QVERIFY(!_success);
|
QVERIFY(!_success);
|
||||||
QVERIFY(sizes.size() == 0 ); // No quota info in the XML
|
QVERIFY(sizes.size() == 0 ); // No quota info in the XML
|
||||||
@@ -229,7 +229,7 @@ private slots:
|
|||||||
this, SLOT(slotFinishedSuccessfully()) );
|
this, SLOT(slotFinishedSuccessfully()) );
|
||||||
|
|
||||||
QHash <QString, ExtraFolderInfo> sizes;
|
QHash <QString, ExtraFolderInfo> sizes;
|
||||||
QVERIFY(false == parser.parse( testXml, &sizes, "/oc/remote.php/webdav/sharefolder" )); // verify false
|
QVERIFY(false == parser.parse( testXml, &sizes, "/oc/remote.php/dav/sharefolder" )); // verify false
|
||||||
|
|
||||||
QVERIFY(!_success);
|
QVERIFY(!_success);
|
||||||
QVERIFY(sizes.size() == 0 ); // No quota info in the XML
|
QVERIFY(sizes.size() == 0 ); // No quota info in the XML
|
||||||
@@ -242,7 +242,7 @@ private slots:
|
|||||||
const QByteArray testXml = "<?xml version='1.0' encoding='utf-8'?>"
|
const QByteArray testXml = "<?xml version='1.0' encoding='utf-8'?>"
|
||||||
"<d:multistatus xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\">"
|
"<d:multistatus xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\">"
|
||||||
"<d:response>"
|
"<d:response>"
|
||||||
"<d:href>/oc/remote.php/webdav/sharefolder/</d:href>"
|
"<d:href>/oc/remote.php/dav/sharefolder/</d:href>"
|
||||||
"<d:propstat>"
|
"<d:propstat>"
|
||||||
"<d:prop>"
|
"<d:prop>"
|
||||||
"<oc:id>00004213ocobzus5kn6s</oc:id>"
|
"<oc:id>00004213ocobzus5kn6s</oc:id>"
|
||||||
@@ -268,7 +268,7 @@ private slots:
|
|||||||
this, SLOT(slotFinishedSuccessfully()) );
|
this, SLOT(slotFinishedSuccessfully()) );
|
||||||
|
|
||||||
QHash <QString, ExtraFolderInfo> sizes;
|
QHash <QString, ExtraFolderInfo> sizes;
|
||||||
QVERIFY(!parser.parse( testXml, &sizes, "/oc/remote.php/webdav/sharefolder" ));
|
QVERIFY(!parser.parse( testXml, &sizes, "/oc/remote.php/dav/sharefolder" ));
|
||||||
QVERIFY(!_success);
|
QVERIFY(!_success);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,7 +276,7 @@ private slots:
|
|||||||
const QByteArray testXml = "<?xml version='1.0' encoding='utf-8'?>"
|
const QByteArray testXml = "<?xml version='1.0' encoding='utf-8'?>"
|
||||||
"<d:multistatus xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\">"
|
"<d:multistatus xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\">"
|
||||||
"<d:response>"
|
"<d:response>"
|
||||||
"<d:href>http://127.0.0.1:81/oc/remote.php/webdav/sharefolder/</d:href>"
|
"<d:href>http://127.0.0.1:81/oc/remote.php/dav/sharefolder/</d:href>"
|
||||||
"<d:propstat>"
|
"<d:propstat>"
|
||||||
"<d:prop>"
|
"<d:prop>"
|
||||||
"<oc:id>00004213ocobzus5kn6s</oc:id>"
|
"<oc:id>00004213ocobzus5kn6s</oc:id>"
|
||||||
@@ -300,7 +300,7 @@ private slots:
|
|||||||
"</d:propstat>"
|
"</d:propstat>"
|
||||||
"</d:response>"
|
"</d:response>"
|
||||||
"<d:response>"
|
"<d:response>"
|
||||||
"<d:href>http://127.0.0.1:81/oc/remote.php/webdav/sharefolder/quitte.pdf</d:href>"
|
"<d:href>http://127.0.0.1:81/oc/remote.php/dav/sharefolder/quitte.pdf</d:href>"
|
||||||
"<d:propstat>"
|
"<d:propstat>"
|
||||||
"<d:prop>"
|
"<d:prop>"
|
||||||
"<oc:id>00004215ocobzus5kn6s</oc:id>"
|
"<oc:id>00004215ocobzus5kn6s</oc:id>"
|
||||||
@@ -333,7 +333,7 @@ private slots:
|
|||||||
this, SLOT(slotFinishedSuccessfully()) );
|
this, SLOT(slotFinishedSuccessfully()) );
|
||||||
|
|
||||||
QHash <QString, ExtraFolderInfo> sizes;
|
QHash <QString, ExtraFolderInfo> sizes;
|
||||||
QVERIFY(false == parser.parse( testXml, &sizes, "/oc/remote.php/webdav/sharefolder" ));
|
QVERIFY(false == parser.parse( testXml, &sizes, "/oc/remote.php/dav/sharefolder" ));
|
||||||
QVERIFY(!_success);
|
QVERIFY(!_success);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,7 +398,7 @@ private slots:
|
|||||||
this, SLOT(slotFinishedSuccessfully()) );
|
this, SLOT(slotFinishedSuccessfully()) );
|
||||||
|
|
||||||
QHash <QString, ExtraFolderInfo> sizes;
|
QHash <QString, ExtraFolderInfo> sizes;
|
||||||
QVERIFY(false == parser.parse( testXml, &sizes, "/oc/remote.php/webdav/sharefolder" ));
|
QVERIFY(false == parser.parse( testXml, &sizes, "/oc/remote.php/dav/sharefolder" ));
|
||||||
QVERIFY(!_success);
|
QVERIFY(!_success);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,7 +406,7 @@ private slots:
|
|||||||
const QByteArray testXml = "<?xml version='1.0' encoding='utf-8'?>"
|
const QByteArray testXml = "<?xml version='1.0' encoding='utf-8'?>"
|
||||||
"<d:multistatus xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\">"
|
"<d:multistatus xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\">"
|
||||||
"<d:response>"
|
"<d:response>"
|
||||||
"<d:href>/oc/remote.php/webdav/sharefolder/</d:href>"
|
"<d:href>/oc/remote.php/dav/sharefolder/</d:href>"
|
||||||
"<d:propstat>"
|
"<d:propstat>"
|
||||||
"<d:prop>"
|
"<d:prop>"
|
||||||
"<oc:id>00004213ocobzus5kn6s</oc:id>"
|
"<oc:id>00004213ocobzus5kn6s</oc:id>"
|
||||||
@@ -430,7 +430,7 @@ private slots:
|
|||||||
"</d:propstat>"
|
"</d:propstat>"
|
||||||
"</d:response>"
|
"</d:response>"
|
||||||
"<d:response>"
|
"<d:response>"
|
||||||
"<d:href>/oc/remote.php/webdav/sharefolder/../sharefolder/quitte.pdf</d:href>"
|
"<d:href>/oc/remote.php/dav/sharefolder/../sharefolder/quitte.pdf</d:href>"
|
||||||
"<d:propstat>"
|
"<d:propstat>"
|
||||||
"<d:prop>"
|
"<d:prop>"
|
||||||
"<oc:id>00004215ocobzus5kn6s</oc:id>"
|
"<oc:id>00004215ocobzus5kn6s</oc:id>"
|
||||||
@@ -463,16 +463,16 @@ private slots:
|
|||||||
this, SLOT(slotFinishedSuccessfully()) );
|
this, SLOT(slotFinishedSuccessfully()) );
|
||||||
|
|
||||||
QHash <QString, ExtraFolderInfo> sizes;
|
QHash <QString, ExtraFolderInfo> sizes;
|
||||||
QVERIFY(parser.parse( testXml, &sizes, "/oc/remote.php/webdav/sharefolder" ));
|
QVERIFY(parser.parse( testXml, &sizes, "/oc/remote.php/dav/sharefolder" ));
|
||||||
|
|
||||||
QVERIFY(_success);
|
QVERIFY(_success);
|
||||||
QCOMPARE(sizes.size(), 1 ); // Quota info in the XML
|
QCOMPARE(sizes.size(), 1 ); // Quota info in the XML
|
||||||
|
|
||||||
QVERIFY(_items.contains("/oc/remote.php/webdav/sharefolder/quitte.pdf"));
|
QVERIFY(_items.contains("/oc/remote.php/dav/sharefolder/quitte.pdf"));
|
||||||
QVERIFY(_items.contains("/oc/remote.php/webdav/sharefolder"));
|
QVERIFY(_items.contains("/oc/remote.php/dav/sharefolder"));
|
||||||
QVERIFY(_items.size() == 2 );
|
QVERIFY(_items.size() == 2 );
|
||||||
|
|
||||||
QVERIFY(_subdirs.contains("/oc/remote.php/webdav/sharefolder/"));
|
QVERIFY(_subdirs.contains("/oc/remote.php/dav/sharefolder/"));
|
||||||
QVERIFY(_subdirs.size() == 1);
|
QVERIFY(_subdirs.size() == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,7 +480,7 @@ private slots:
|
|||||||
const QByteArray testXml = "<?xml version='1.0' encoding='utf-8'?>"
|
const QByteArray testXml = "<?xml version='1.0' encoding='utf-8'?>"
|
||||||
"<d:multistatus xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\">"
|
"<d:multistatus xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\">"
|
||||||
"<d:response>"
|
"<d:response>"
|
||||||
"<d:href>/oc/remote.php/webdav/sharefolder/</d:href>"
|
"<d:href>/oc/remote.php/dav/sharefolder/</d:href>"
|
||||||
"<d:propstat>"
|
"<d:propstat>"
|
||||||
"<d:prop>"
|
"<d:prop>"
|
||||||
"<oc:id>00004213ocobzus5kn6s</oc:id>"
|
"<oc:id>00004213ocobzus5kn6s</oc:id>"
|
||||||
@@ -504,7 +504,7 @@ private slots:
|
|||||||
"</d:propstat>"
|
"</d:propstat>"
|
||||||
"</d:response>"
|
"</d:response>"
|
||||||
"<d:response>"
|
"<d:response>"
|
||||||
"<d:href>/oc/remote.php/webdav/sharefolder/../quitte.pdf</d:href>"
|
"<d:href>/oc/remote.php/dav/sharefolder/../quitte.pdf</d:href>"
|
||||||
"<d:propstat>"
|
"<d:propstat>"
|
||||||
"<d:prop>"
|
"<d:prop>"
|
||||||
"<oc:id>00004215ocobzus5kn6s</oc:id>"
|
"<oc:id>00004215ocobzus5kn6s</oc:id>"
|
||||||
@@ -537,7 +537,7 @@ private slots:
|
|||||||
this, SLOT(slotFinishedSuccessfully()) );
|
this, SLOT(slotFinishedSuccessfully()) );
|
||||||
|
|
||||||
QHash <QString, ExtraFolderInfo> sizes;
|
QHash <QString, ExtraFolderInfo> sizes;
|
||||||
QVERIFY(!parser.parse( testXml, &sizes, "/oc/remote.php/webdav/sharefolder" ));
|
QVERIFY(!parser.parse( testXml, &sizes, "/oc/remote.php/dav/sharefolder" ));
|
||||||
|
|
||||||
QVERIFY(!_success);
|
QVERIFY(!_success);
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user