mirror of
https://github.com/chylex/Nextcloud-Desktop.git
synced 2026-04-03 18:11:32 +02:00
Compare commits
301 Commits
v3.3.0-rc1
...
stable-3.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
85b5153bad | ||
|
|
52bd0d1915 | ||
|
|
7d137826b3 | ||
|
|
99d6a48b38 | ||
|
|
6f619540b7 | ||
|
|
5790d78145 | ||
|
|
4bac4d9c43 | ||
|
|
024eefb0ee | ||
|
|
6428c0a185 | ||
|
|
5bca9bf43e | ||
|
|
c21b1c431e | ||
|
|
aefdce86d6 | ||
|
|
52c1ed95ee | ||
|
|
757870b542 | ||
|
|
a7b5dacea5 | ||
|
|
398a2bea41 | ||
|
|
3a743f5db3 | ||
|
|
47301e0b37 | ||
|
|
b108ccaada | ||
|
|
49e1788fdb | ||
|
|
3a9e9a3c3e | ||
|
|
5aec22b38a | ||
|
|
0c053894d1 | ||
|
|
947c8030f7 | ||
|
|
15cbe443ae | ||
|
|
6b5e9cbc72 | ||
|
|
0b0712fc75 | ||
|
|
059b5f78c9 | ||
|
|
7420ef8f1e | ||
|
|
df09ea1cb9 | ||
|
|
d14c4a6344 | ||
|
|
e71703789f | ||
|
|
a575c01476 | ||
|
|
9cbd3cc7e1 | ||
|
|
2527cac1b1 | ||
|
|
54a0a1dbdd | ||
|
|
c162ae4f24 | ||
|
|
cfe72a9a97 | ||
|
|
e708d8ef82 | ||
|
|
0c8523cdef | ||
|
|
962cf593a4 | ||
|
|
eac2c9d422 | ||
|
|
511efbd188 | ||
|
|
120db5a47d | ||
|
|
cbdc8fe19c | ||
|
|
d254cbef30 | ||
|
|
770ae11680 | ||
|
|
93535a3e4b | ||
|
|
261a8c7d75 | ||
|
|
dff10a00ee | ||
|
|
04aed0a074 | ||
|
|
2fc073ac58 | ||
|
|
e9ffb3b6c1 | ||
|
|
7aa9907201 | ||
|
|
a8bcfd9927 | ||
|
|
d63e1125a7 | ||
|
|
070e29e9a5 | ||
|
|
cc9fc14f88 | ||
|
|
ed9576b07b | ||
|
|
b53cec3039 | ||
|
|
0eb221daa9 | ||
|
|
5bb38175d3 | ||
|
|
bd8a44310c | ||
|
|
fc072ebbf1 | ||
|
|
82cc041c8b | ||
|
|
f17dde7bbc | ||
|
|
c75f7115c5 | ||
|
|
c1575d5570 | ||
|
|
8224eba86c | ||
|
|
58f6e2aeb0 | ||
|
|
2a2339cdf7 | ||
|
|
c9876c01d8 | ||
|
|
a698e2a339 | ||
|
|
61e9f9ed78 | ||
|
|
4718e900fd | ||
|
|
e0f2444645 | ||
|
|
21521352f9 | ||
|
|
ef6767390d | ||
|
|
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
|
||||
Icon[bg_BG]=@APPLICATION_ICON_NAME@
|
||||
Name[bg_BG]=@APPLICATION_NAME@ десктоп клиент за синхронизиране
|
||||
Comment[bg_BG]=@APPLICATION_NAME@ десктоп клиент за синхронизиране
|
||||
GenericName[bg_BG]=Синхронизиране на папка
|
||||
|
||||
@@ -22,5 +22,6 @@ Icon=@APPLICATION_EXECUTABLE@
|
||||
|
||||
# Translations
|
||||
Icon[cy_GB]=@APPLICATION_ICON_NAME@
|
||||
Name[cy_GB]=@APPLICATION_NAME@ cleient cydweddu bwrdd gwaith
|
||||
Comment[cy_GB]=@APPLICATION_NAME@ cleient cydweddu bwrdd gwaith
|
||||
GenericName[cy_GB]=Cydweddu Ffolder
|
||||
|
||||
@@ -21,7 +21,7 @@ Icon=@APPLICATION_EXECUTABLE@
|
||||
|
||||
|
||||
# Translations
|
||||
Icon[de]=@APPLICATION_ICON_NAME@
|
||||
Name[de]=@APPLICATION_NAME@ Desktop
|
||||
Comment[de]=@APPLICATION_NAME@ Client zur Desktop-Synchronisierung
|
||||
GenericName[de]=Ordner-Synchronisation
|
||||
Icon[de_DE]=@APPLICATION_ICON_NAME@
|
||||
Name[de_DE]=@APPLICATION_NAME@ Client zur Desktop-Synchronisierung
|
||||
Comment[de_DE]=@APPLICATION_NAME@ Client zur Desktop-Synchronisierung
|
||||
GenericName[de_DE]=Ordnersynchronisierung
|
||||
|
||||
@@ -22,5 +22,6 @@ Icon=@APPLICATION_EXECUTABLE@
|
||||
|
||||
# Translations
|
||||
Icon[en_GB]=@APPLICATION_ICON_NAME@
|
||||
Name[en_GB]=@APPLICATION_NAME@ Desktop
|
||||
Comment[en_GB]=@APPLICATION_NAME@ desktop synchronisation client
|
||||
GenericName[en_GB]=Folder Sync
|
||||
|
||||
@@ -22,5 +22,6 @@ Icon=@APPLICATION_EXECUTABLE@
|
||||
|
||||
# Translations
|
||||
Icon[hr]=@APPLICATION_ICON_NAME@
|
||||
Name[hr]=@APPLICATION_NAME@ Desktop
|
||||
Comment[hr]=@APPLICATION_NAME@ klijent za sinkronizaciju računala
|
||||
GenericName[hr]=Sinkronizacija mapa
|
||||
|
||||
24
.tx/nextcloud.client-desktop/id_translation
Normal file
24
.tx/nextcloud.client-desktop/id_translation
Normal file
@@ -0,0 +1,24 @@
|
||||
[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
|
||||
GenericName[id]=Sinkronisasi Folder
|
||||
@@ -22,5 +22,6 @@ Icon=@APPLICATION_EXECUTABLE@
|
||||
|
||||
# Translations
|
||||
Icon[ko]=@APPLICATION_ICON_NAME@
|
||||
Name[ko]=@APPLICATION_NAME@ 데스크탑
|
||||
Comment[ko]=@APPLICATION_NAME@ 데스크톱 동기화 클라이언트
|
||||
GenericName[ko]=폴더 동기화
|
||||
|
||||
@@ -22,5 +22,6 @@ Icon=@APPLICATION_EXECUTABLE@
|
||||
|
||||
# Translations
|
||||
Icon[mk]=@APPLICATION_ICON_NAME@
|
||||
Name[mk]=@APPLICATION_NAME@ Десктоп
|
||||
Comment[mk]=@APPLICATION_NAME@ клиент за синхронизација на компјутер
|
||||
GenericName[mk]=Папка за синхронизација
|
||||
|
||||
@@ -22,5 +22,6 @@ Icon=@APPLICATION_EXECUTABLE@
|
||||
|
||||
# Translations
|
||||
Icon[nb_NO]=@APPLICATION_ICON_NAME@
|
||||
Name[nb_NO]=@APPLICATION_NAME@ skrivebord
|
||||
Comment[nb_NO]=@APPLICATION_NAME@ klient for synkroinisering
|
||||
GenericName[nb_NO]=Mappe synkroinisering
|
||||
|
||||
@@ -22,5 +22,6 @@ Icon=@APPLICATION_EXECUTABLE@
|
||||
|
||||
# Translations
|
||||
Icon[oc]=@APPLICATION_ICON_NAME@
|
||||
Name[oc]=@APPLICATION_NAME@ Burèu
|
||||
Comment[oc]=@APPLICATION_NAME@ client de sincronizacion
|
||||
GenericName[oc]=Sincro. dossièr
|
||||
|
||||
@@ -22,5 +22,6 @@ Icon=@APPLICATION_EXECUTABLE@
|
||||
|
||||
# Translations
|
||||
Icon[ro]=@APPLICATION_ICON_NAME@
|
||||
Name[ro]=@Numele_aplicației@ Client de sincronizare pentru PC
|
||||
Comment[ro]=@APPLICATION_NAME@ client de sincronizare pentru desktop
|
||||
GenericName[ro]=Sincronizare director
|
||||
|
||||
@@ -22,5 +22,6 @@ Icon=@APPLICATION_EXECUTABLE@
|
||||
|
||||
# Translations
|
||||
Icon[ru]=@APPLICATION_ICON_NAME@
|
||||
Name[ru]=@APPLICATION_NAME@ Desktop
|
||||
Comment[ru]=Клиент синхронизации @APPLICATION_NAME@ для ПК
|
||||
GenericName[ru]=Синхронизация папок
|
||||
|
||||
@@ -22,5 +22,6 @@ Icon=@APPLICATION_EXECUTABLE@
|
||||
|
||||
# Translations
|
||||
Icon[sc]=@NÙMENE_ICONA_APLICATZIONE@
|
||||
Name[sc]=@NÙMENE_APLICATZIONE@ Iscrivania
|
||||
Comment[sc]=@NÙMENE_APLICATZIONE@ cliente de sincronizatzione iscrivania
|
||||
GenericName[sc]=Sincronizadore de cartellas
|
||||
|
||||
@@ -22,5 +22,6 @@ Icon=@APPLICATION_EXECUTABLE@
|
||||
|
||||
# Translations
|
||||
Icon[sv]=@APPLICATION_ICON_NAME@
|
||||
Name[sv]=@APPLICATION_NAME@ Skrivbord
|
||||
Comment[sv]=@APPLICATION_NAME@ desktopssynkroniseringsklient
|
||||
GenericName[sv]=Mappsynkronisering
|
||||
|
||||
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 )
|
||||
|
||||
include(ECMCoverageOption)
|
||||
|
||||
if(NOT CRASHREPORTER_EXECUTABLE)
|
||||
set(CRASHREPORTER_EXECUTABLE "${APPLICATION_EXECUTABLE}_crash_reporter")
|
||||
endif()
|
||||
|
||||
@@ -4,7 +4,7 @@ The :computer: Nextcloud Desktop Client is a tool to synchronize files from Next
|
||||
with your computer.
|
||||
|
||||
<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>
|
||||
|
||||
## :blue_heart: :tada: Contributing
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
set( MIRALL_VERSION_MAJOR 3 )
|
||||
set( MIRALL_VERSION_MINOR 2 )
|
||||
set( MIRALL_VERSION_PATCH 81 )
|
||||
set( MIRALL_VERSION_MINOR 3 )
|
||||
set( MIRALL_VERSION_PATCH 6 )
|
||||
set( MIRALL_VERSION_YEAR 2021 )
|
||||
set( MIRALL_SOVERSION 0 )
|
||||
|
||||
# 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_PATCH 0)
|
||||
|
||||
|
||||
@@ -15,10 +15,10 @@ OBS_PROJECT_BETA=home:ivaradi:beta
|
||||
OBS_PACKAGE=nextcloud-desktop
|
||||
|
||||
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"
|
||||
else
|
||||
UBUNTU_DISTRIBUTIONS="focal groovy hirsute impish"
|
||||
UBUNTU_DISTRIBUTIONS="focal hirsute impish"
|
||||
DEBIAN_DISTRIBUTIONS="testing"
|
||||
fi
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# Always enable the new 10.10 finder plugin if available
|
||||
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
|
||||
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
|
||||
sleep 10s
|
||||
# enable it
|
||||
|
||||
@@ -2,5 +2,6 @@
|
||||
|
||||
# kill the old version. see issue #2044
|
||||
killall @APPLICATION_EXECUTABLE@
|
||||
killall @APPLICATION_NAME@
|
||||
|
||||
exit 0
|
||||
|
||||
46
appveyor.ini
46
appveyor.ini
@@ -1,7 +1,6 @@
|
||||
[General]
|
||||
Branch = master
|
||||
ShallowClone = True
|
||||
Command=craft
|
||||
|
||||
# Variables defined here override the default value
|
||||
# The variable names are casesensitive
|
||||
@@ -13,33 +12,42 @@ CreateCache = False
|
||||
# Settings applicable for all Crafts matrices
|
||||
# Settings are Category/key=value
|
||||
# Category is case sensitive
|
||||
|
||||
[GeneralSettings]
|
||||
General/EMERGE_PKGDSTDIR=${Variables:APPVEYOR_BUILD_FOLDER}/binaries
|
||||
Paths/python = C:\Python36
|
||||
Paths/python27 = C:\Python27
|
||||
|
||||
## This is the location of your python installation.
|
||||
## 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
|
||||
ShortPath/Enabled = False
|
||||
ShortPath/EnableJunctions = True
|
||||
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/CreateCache = ${Variables:CreateCache}
|
||||
; Packager/RepositoryUrl = https://files.kde.org/craft/
|
||||
Packager/PackageType = PortablePackager
|
||||
Packager/RepositoryUrl = http://ftp.acc.umu.se/mirror/kde.org/files/craft/master/
|
||||
Compile/BuildType = RelWithDebInfo
|
||||
ContinuousIntegration/Enabled = True
|
||||
Packager/CacheDir = ${Variables:Root}\cache
|
||||
|
||||
[BlueprintSettings]
|
||||
# don't try to pip install on the ci
|
||||
python-modules.ignored = True
|
||||
nextcloud-client.buildTests = True
|
||||
binary/mysql.useMariaDB = False
|
||||
|
||||
libs/qt5.version = 5.9.3
|
||||
craft/craft-core.version = master
|
||||
|
||||
[windows-msvc2017_64-cl]
|
||||
General/ABI = windows-msvc2017_64-cl
|
||||
|
||||
[windows-msvc2017_32-cl]
|
||||
General/ABI = windows-msvc2017_32-cl
|
||||
[windows-msvc2019_64-cl]
|
||||
QtSDK/Compiler = msvc2019_64
|
||||
General/ABI = windows-msvc2019_64-cl
|
||||
|
||||
47
appveyor.yml
47
appveyor.yml
@@ -1,50 +1,49 @@
|
||||
version: '{build}-{branch}'
|
||||
|
||||
image: Visual Studio 2019
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
clone_depth: 50
|
||||
|
||||
clone_depth: 1
|
||||
|
||||
init:
|
||||
- ps: |
|
||||
function craft($target) {
|
||||
& 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
|
||||
function craft() {
|
||||
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}
|
||||
}
|
||||
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:
|
||||
- ps: |
|
||||
#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"
|
||||
|
||||
craft $env:TARGET -i craft
|
||||
craft $env:TARGET --install-deps owncloud-client
|
||||
& 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 craft
|
||||
craft --install-deps nextcloud-client
|
||||
craft nsis
|
||||
|
||||
build_script:
|
||||
- ps: |
|
||||
craft $env:TARGET --no-cache --src-dir $env:APPVEYOR_BUILD_FOLDER owncloud-client
|
||||
|
||||
after_build:
|
||||
- ps: |
|
||||
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 }
|
||||
craft --src-dir $env:APPVEYOR_BUILD_FOLDER nextcloud-client
|
||||
craft --package --src-dir $env:APPVEYOR_BUILD_FOLDER nextcloud-client
|
||||
cp C:\CraftMaster\windows-msvc2019_64-cl\tmp\*.7z $env:APPVEYOR_BUILD_FOLDER
|
||||
cp C:\CraftMaster\windows-msvc2019_64-cl\tmp\*.exe $env:APPVEYOR_BUILD_FOLDER
|
||||
|
||||
test_script:
|
||||
- ps: |
|
||||
craft $env:TARGET --src-dir $env:APPVEYOR_BUILD_FOLDER --test owncloud-client
|
||||
crafttests --test --src-dir $env:APPVEYOR_BUILD_FOLDER nextcloud-client
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- TARGET: windows-msvc2017_32-cl
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
||||
- TARGET: windows-msvc2017_64-cl
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
||||
- TARGET: windows-msvc2019_64-cl
|
||||
|
||||
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":{
|
||||
"core":{
|
||||
"pollinterval":60,
|
||||
"webdav-root":"remote.php/webdav"
|
||||
"webdav-root":"remote.php/dav"
|
||||
},
|
||||
"dav":{
|
||||
"chunking":"1.0"
|
||||
|
||||
@@ -32,10 +32,9 @@ itself. Should the silent update fail, the client offers a manual download.
|
||||
macOS
|
||||
^^^^^
|
||||
|
||||
If a new update is available, the Nextcloud client initializes a pop-up dialog
|
||||
to alert you of the update and requesting that you update to the latest
|
||||
version. Due to their use of the Sparkle frameworks, this is the default
|
||||
process for macOS applications.
|
||||
There is no automatic updater on macOS. If a new update is available,
|
||||
the Nextcloud client initializes a pop-up dialog to alert you of the
|
||||
update and requesting that you update to the latest version manually.
|
||||
|
||||
Linux
|
||||
^^^^^
|
||||
@@ -96,14 +95,6 @@ To prevent automatic updates and disallow manual overrides:
|
||||
.. 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
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
||||
@@ -48,9 +48,9 @@ copyright = u'2013-2021, The Nextcloud developers'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '3.2'
|
||||
version = '3.3'
|
||||
# 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
|
||||
# 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
|
||||
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
|
||||
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
|
||||
improvements.
|
||||
|
||||
* Virtual Files on Windows
|
||||
* Support for the user status from the server
|
||||
* Many improvements to the sync engine
|
||||
* Make the end-to-end encryption work more reliable
|
||||
* Improve sync performance
|
||||
* Main dialog will be a regular window if tray icons are not available on the system.
|
||||
* Virtual files wil be optional. That enables the desktop client to run on older Windows versions.
|
||||
* Improvements for the virtual files on Windows
|
||||
* Network traffic performance improvements
|
||||
* Improvements to the sync engine
|
||||
|
||||
@@ -46,6 +46,9 @@ the server URL.
|
||||
|
||||
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]``
|
||||
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>``
|
||||
Uses ``server`` as HTTP proxy.
|
||||
|
||||
``--nonshib``
|
||||
Uses Non Shibboleth WebDAV Authentication
|
||||
|
||||
``--davpath [path]``
|
||||
Overrides the WebDAV Path with ``path``
|
||||
|
||||
``--exclude [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
|
||||
``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::
|
||||
|
||||
$ nextcloudcmd --httpproxy http://192.168.178.1:8080 \
|
||||
$ nextcloudcmd --httpproxy http://192.168.178.1:8080 --path /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
|
||||
been specified on the command line or ``-n`` has been passed.
|
||||
@@ -120,5 +117,5 @@ Example
|
||||
|
||||
::
|
||||
|
||||
$ nextcloudcmd /home/user/<my_sync_folder> \
|
||||
https://<username>:<secret>@<server_address>/remote.php/webdav/<Directory_that_has_been_created>
|
||||
$ nextcloudcmd --path /<Directory_that_has_been_created> /home/user/<my_sync_folder> \
|
||||
https://<username>:<secret>@<server_address>
|
||||
|
||||
@@ -34,7 +34,7 @@ Identifying Basic Functionality Problems
|
||||
|
||||
For example, if your Nextcloud instance is installed at
|
||||
``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
|
||||
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.
|
||||
|
||||
.. 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.
|
||||
|
||||
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]``
|
||||
Use ``user`` as the login name.
|
||||
|
||||
@@ -50,12 +53,6 @@ OPTIONS
|
||||
``—httpproxy http://[user@pass:]<server>:<port>``
|
||||
Uses ``server`` as HTTP proxy.
|
||||
|
||||
``—nonshib``
|
||||
Uses Non Shibboleth WebDAV Authentication
|
||||
|
||||
``—davpath [path]``
|
||||
Overrides the WebDAV Path with ``path``
|
||||
|
||||
``—exclude [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``,
|
||||
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 \
|
||||
https://server/nextcloud/remote.php/webdav/Music
|
||||
https://server/nextcloud
|
||||
|
||||
``nextcloudcmd`` will enquire user name and password, unless they have
|
||||
been specified on the command line or ``-n`` (see `netrc(5)`) has been passed.
|
||||
|
||||
Using the legacy scheme, it would be::
|
||||
|
||||
$ nextcloudcmd —httpproxy http://192.168.178.1:8080 \
|
||||
$ nextcloudcmd —httpproxy http://192.168.178.1:8080 --path /Music \
|
||||
$HOME/media/music \
|
||||
nextclouds://server/nextcloud/remote.php/webdav/Music
|
||||
ownclouds://server/nextcloud
|
||||
|
||||
|
||||
BUGS
|
||||
|
||||
@@ -6,5 +6,6 @@
|
||||
<file>theme/Style/Style.qml</file>
|
||||
<file>theme/Style/qmldir</file>
|
||||
<file>src/gui/tray/ActivityActionButton.qml</file>
|
||||
<file>src/gui/tray/ActivityItem.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
121
src/cmd/cmd.cpp
121
src/cmd/cmd.cpp
@@ -66,6 +66,7 @@ struct CmdOptions
|
||||
{
|
||||
QString source_dir;
|
||||
QString target_url;
|
||||
QString remotePath = QStringLiteral("/");
|
||||
QString config_directory;
|
||||
QString user;
|
||||
QString password;
|
||||
@@ -75,10 +76,8 @@ struct CmdOptions
|
||||
bool useNetrc;
|
||||
bool interactive;
|
||||
bool ignoreHiddenFiles;
|
||||
bool nonShib;
|
||||
QString exclude;
|
||||
QString unsyncedfolders;
|
||||
QString davPath;
|
||||
int restartTimes;
|
||||
int downlimit;
|
||||
int uplimit;
|
||||
@@ -188,14 +187,13 @@ void help()
|
||||
std::cout << " --password, -p [pass] Use [pass] as password" << std::endl;
|
||||
std::cout << " -n Use netrc (5) for login" << std::endl;
|
||||
std::cout << " --non-interactive Do not block execution with interaction" << std::endl;
|
||||
std::cout << " --nonshib Use Non Shibboleth WebDAV authentication" << std::endl;
|
||||
std::cout << " --davpath [path] Custom themed dav path, overrides --nonshib" << std::endl;
|
||||
std::cout << " --max-sync-retries [n] Retries maximum n times (default to 3)" << std::endl;
|
||||
std::cout << " --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 << " -h Sync hidden files, do not ignore them" << std::endl;
|
||||
std::cout << " --version, -v Display version and exit" << 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;
|
||||
exit(0);
|
||||
}
|
||||
@@ -263,10 +261,6 @@ void parseOptions(const QStringList &app_args, CmdOptions *options)
|
||||
options->exclude = it.next();
|
||||
} else if (option == "--unsyncedfolders" && !it.peekNext().startsWith("-")) {
|
||||
options->unsyncedfolders = it.next();
|
||||
} else if (option == "--nonshib") {
|
||||
options->nonShib = true;
|
||||
} else if (option == "--davpath" && !it.peekNext().startsWith("-")) {
|
||||
options->davPath = it.next();
|
||||
} else if (option == "--max-sync-retries" && !it.peekNext().startsWith("-")) {
|
||||
options->restartTimes = it.next().toInt();
|
||||
} else if (option == "--uplimit" && !it.peekNext().startsWith("-")) {
|
||||
@@ -276,7 +270,10 @@ void parseOptions(const QStringList &app_args, CmdOptions *options)
|
||||
} else if (option == "--logdebug") {
|
||||
Logger::instance()->setLogFile("-");
|
||||
Logger::instance()->setLogDebug(true);
|
||||
} else {
|
||||
} else if (option == "--path" && !it.peekNext().startsWith("-")) {
|
||||
options->remotePath = it.next();
|
||||
}
|
||||
else {
|
||||
help();
|
||||
}
|
||||
}
|
||||
@@ -312,6 +309,9 @@ void selectiveSyncFixup(OCC::SyncJournalDb *journal, const QStringList &newList)
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
SetDllDirectory(L"");
|
||||
#endif
|
||||
QCoreApplication app(argc, argv);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
@@ -328,7 +328,6 @@ int main(int argc, char **argv)
|
||||
options.useNetrc = false;
|
||||
options.interactive = true;
|
||||
options.ignoreHiddenFiles = false; // Default is to sync hidden files
|
||||
options.nonShib = false;
|
||||
options.restartTimes = 3;
|
||||
options.uplimit = 0;
|
||||
options.downlimit = 0;
|
||||
@@ -347,24 +346,15 @@ int main(int argc, char **argv)
|
||||
qFatal("Could not initialize account!");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
// check if the webDAV path was added to the url and append if not.
|
||||
if (!options.target_url.endsWith("/")) {
|
||||
options.target_url.append("/");
|
||||
|
||||
if (options.target_url.contains("/webdav", Qt::CaseInsensitive) || options.target_url.contains("/dav", Qt::CaseInsensitive)) {
|
||||
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) {
|
||||
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);
|
||||
QUrl hostUrl = QUrl::fromUserInput((options.target_url.endsWith(QLatin1Char('/')) || options.target_url.endsWith(QLatin1Char('\\'))) ? options.target_url.chopped(1) : options.target_url);
|
||||
|
||||
// Order of retrieval attempt (later attempts override earlier ones):
|
||||
// 1. From URL
|
||||
@@ -372,8 +362,8 @@ int main(int argc, char **argv)
|
||||
// 3. From netrc (if enabled)
|
||||
// 4. From prompt (if interactive)
|
||||
|
||||
QString user = url.userName();
|
||||
QString password = url.password();
|
||||
QString user = hostUrl.userName();
|
||||
QString password = hostUrl.password();
|
||||
|
||||
if (!options.user.isEmpty()) {
|
||||
user = options.user;
|
||||
@@ -386,7 +376,7 @@ int main(int argc, char **argv)
|
||||
if (options.useNetrc) {
|
||||
NetrcParser parser;
|
||||
if (parser.parse()) {
|
||||
NetrcParser::LoginPair pair = parser.find(url.host());
|
||||
NetrcParser::LoginPair pair = parser.find(hostUrl.host());
|
||||
user = pair.first;
|
||||
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
|
||||
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.setPassword(QString());
|
||||
|
||||
// Remote folders typically start with a / and don't end with one
|
||||
QString folder = "/" + splitted.value(1);
|
||||
if (folder.endsWith("/") && folder != "/") {
|
||||
folder.chop(1);
|
||||
}
|
||||
const QString folder = options.remotePath;
|
||||
|
||||
if (!options.proxy.isNull()) {
|
||||
QString host;
|
||||
@@ -458,44 +439,36 @@ int main(int argc, char **argv)
|
||||
}
|
||||
#endif
|
||||
|
||||
account->setUrl(url);
|
||||
account->setUrl(hostUrl);
|
||||
account->setSslErrorHandler(sslErrorHandler);
|
||||
|
||||
// Perform a call to get the capabilities.
|
||||
if (!options.nonShib) {
|
||||
// Do not do it if '--nonshib' was passed. This mean we should only connect to the 'nonshib'
|
||||
// dav endpoint. Since we do not get the capabilities, in that case, this has the additional
|
||||
// side effect that chunking-ng will be disabled. (because otherwise it would use the new
|
||||
// 'dav' endpoint instead of the nonshib one (which still use the old chunking)
|
||||
QEventLoop loop;
|
||||
auto *job = new JsonApiJob(account, QLatin1String("ocs/v1.php/cloud/capabilities"));
|
||||
QObject::connect(job, &JsonApiJob::jsonReceived, [&](const QJsonDocument &json) {
|
||||
auto caps = json.object().value("ocs").toObject().value("data").toObject().value("capabilities").toObject();
|
||||
qDebug() << "Server capabilities" << caps;
|
||||
account->setCapabilities(caps.toVariantMap());
|
||||
account->setServerVersion(caps["core"].toObject()["status"].toObject()["version"].toString());
|
||||
loop.quit();
|
||||
});
|
||||
job->start();
|
||||
loop.exec();
|
||||
|
||||
QEventLoop loop;
|
||||
auto *job = new JsonApiJob(account, QLatin1String("ocs/v1.php/cloud/capabilities"));
|
||||
QObject::connect(job, &JsonApiJob::jsonReceived, [&](const QJsonDocument &json) {
|
||||
auto caps = json.object().value("ocs").toObject().value("data").toObject().value("capabilities").toObject();
|
||||
qDebug() << "Server capabilities" << caps;
|
||||
account->setCapabilities(caps.toVariantMap());
|
||||
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();
|
||||
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();
|
||||
|
||||
// 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);
|
||||
|
||||
|
||||
@@ -142,20 +142,26 @@ QByteArray makeChecksumHeader(const QByteArray &checksumType, const QByteArray &
|
||||
return header;
|
||||
}
|
||||
|
||||
QByteArray findBestChecksum(const QByteArray &checksums)
|
||||
QByteArray findBestChecksum(const QByteArray &_checksums)
|
||||
{
|
||||
const auto checksums = QString::fromUtf8(_checksums);
|
||||
int i = 0;
|
||||
// The order of the searches here defines the preference ordering.
|
||||
if (-1 != (i = checksums.indexOf("SHA3-256:"))
|
||||
|| -1 != (i = checksums.indexOf("SHA256:"))
|
||||
|| -1 != (i = checksums.indexOf("SHA1:"))
|
||||
|| -1 != (i = checksums.indexOf("MD5:"))
|
||||
|| -1 != (i = checksums.indexOf("Adler32:"))) {
|
||||
if (-1 != (i = checksums.indexOf(QLatin1String("SHA3-256:"), 0, Qt::CaseInsensitive))
|
||||
|| -1 != (i = checksums.indexOf(QLatin1String("SHA256:"), 0, Qt::CaseInsensitive))
|
||||
|| -1 != (i = checksums.indexOf(QLatin1String("SHA1:"), 0, Qt::CaseInsensitive))
|
||||
|| -1 != (i = checksums.indexOf(QLatin1String("MD5:"), 0, Qt::CaseInsensitive))
|
||||
|| -1 != (i = checksums.indexOf(QLatin1String("ADLER32:"), 0, Qt::CaseInsensitive))) {
|
||||
// Now i is the start of the best checksum
|
||||
// Grab it until the next space or end of string.
|
||||
auto checksum = checksums.mid(i);
|
||||
return checksum.mid(0, checksum.indexOf(" "));
|
||||
// Grab it until the next space or end of xml or end of string.
|
||||
int end = _checksums.indexOf(' ', i);
|
||||
// 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();
|
||||
}
|
||||
|
||||
@@ -328,7 +334,7 @@ ComputeChecksum *ValidateChecksumHeader::prepareStart(const QByteArray &checksum
|
||||
|
||||
if (!parseChecksumHeader(checksumHeader, &_expectedChecksumType, &_expectedChecksum)) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -341,7 +347,6 @@ ComputeChecksum *ValidateChecksumHeader::prepareStart(const QByteArray &checksum
|
||||
|
||||
void ValidateChecksumHeader::start(const QString &filePath, const QByteArray &checksumHeader)
|
||||
{
|
||||
_filePath = filePath;
|
||||
if (auto calculator = prepareStart(checksumHeader))
|
||||
calculator->start(filePath);
|
||||
}
|
||||
@@ -356,11 +361,11 @@ void ValidateChecksumHeader::slotChecksumCalculated(const QByteArray &checksumTy
|
||||
const QByteArray &checksum)
|
||||
{
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
emit validated(checksumType, checksum);
|
||||
|
||||
@@ -163,7 +163,7 @@ public:
|
||||
|
||||
signals:
|
||||
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:
|
||||
void slotChecksumCalculated(const QByteArray &checksumType, const QByteArray &checksum);
|
||||
@@ -173,8 +173,6 @@ private:
|
||||
|
||||
QByteArray _expectedChecksumType;
|
||||
QByteArray _expectedChecksum;
|
||||
|
||||
QString _filePath;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -99,7 +99,7 @@ public:
|
||||
|
||||
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
|
||||
mnemonicdialog.ui
|
||||
tray/ActivityActionButton.qml
|
||||
tray/ActivityItem.qml
|
||||
tray/Window.qml
|
||||
tray/UserLine.qml
|
||||
wizard/flow2authwidget.ui
|
||||
|
||||
@@ -317,7 +317,7 @@ AccountPtr AccountManager::loadAccountHelper(QSettings &settings)
|
||||
qCInfo(lcAccountManager) << "Account for" << acc->url() << "using auth type" << authType;
|
||||
|
||||
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
|
||||
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;
|
||||
|
||||
auto availability = folder->vfs().availability(path);
|
||||
if (availability) {
|
||||
ac = availabilityMenu->addAction(Utility::vfsCurrentAvailabilityText(*availability));
|
||||
ac->setEnabled(false);
|
||||
}
|
||||
|
||||
ac = availabilityMenu->addAction(Utility::vfsPinActionText());
|
||||
ac->setEnabled(!availability || *availability != VfsItemAvailability::AlwaysLocal);
|
||||
connect(ac, &QAction::triggered, this, [this, folder, path] { slotSetSubFolderAvailability(folder, path, PinState::AlwaysLocal); });
|
||||
|
||||
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); });
|
||||
}
|
||||
|
||||
@@ -594,20 +584,11 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos)
|
||||
|
||||
if (folder->virtualFilesEnabled()) {
|
||||
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->setEnabled(!availability || *availability != VfsItemAvailability::AlwaysLocal);
|
||||
connect(ac, &QAction::triggered, this, [this]() { slotSetCurrentFolderAvailability(PinState::AlwaysLocal); });
|
||||
|
||||
ac = availabilityMenu->addAction(Utility::vfsFreeSpaceActionText());
|
||||
ac->setEnabled(!availability
|
||||
|| !(*availability == VfsItemAvailability::OnlineOnly
|
||||
|| *availability == VfsItemAvailability::AllDehydrated));
|
||||
connect(ac, &QAction::triggered, this, [this]() { slotSetCurrentFolderAvailability(PinState::OnlineOnly); });
|
||||
|
||||
ac = menu->addAction(tr("Disable virtual file support …"));
|
||||
|
||||
@@ -227,17 +227,6 @@ void ConnectionValidator::checkServerCapabilities()
|
||||
job->setTimeout(timeoutToUseMsec);
|
||||
QObject::connect(job, &JsonApiJob::jsonReceived, this, &ConnectionValidator::slotCapabilitiesRecieved);
|
||||
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)
|
||||
@@ -260,17 +249,6 @@ void ConnectionValidator::slotCapabilitiesRecieved(const QJsonDocument &json)
|
||||
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()
|
||||
{
|
||||
auto *userInfo = new UserInfo(_accountState.data(), true, true, this);
|
||||
|
||||
@@ -60,8 +60,7 @@ namespace OCC {
|
||||
+---------------------------+
|
||||
|
|
||||
+-> checkServerCapabilities --------------v (in parallel)
|
||||
JsonApiJob (cloud/capabilities) JsonApiJob (ocs/v1.php/config)
|
||||
| +-> ocsConfigReceived
|
||||
JsonApiJob (cloud/capabilities)
|
||||
+-> slotCapabilitiesRecieved -+
|
||||
|
|
||||
+---------------------------------+
|
||||
@@ -131,7 +130,6 @@ private:
|
||||
void reportResult(Status status);
|
||||
void checkServerCapabilities();
|
||||
void fetchUser();
|
||||
static void ocsConfigReceived(const QJsonDocument &json, AccountPtr account);
|
||||
|
||||
/** 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
|
||||
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)
|
||||
QNetworkRequest req;
|
||||
@@ -98,6 +99,11 @@ void Flow2Auth::fetchNewToken(const TokenAction action)
|
||||
&& !json.isEmpty()) {
|
||||
pollToken = json.value("poll").toObject().value("token").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();
|
||||
}
|
||||
|
||||
@@ -200,6 +206,11 @@ void Flow2Auth::slotPollTimerTimeout()
|
||||
if (reply->error() == QNetworkReply::NoError && jsonParseError.error == QJsonParseError::NoError
|
||||
&& !json.isEmpty()) {
|
||||
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();
|
||||
appPassword = json["appPassword"].toString();
|
||||
}
|
||||
|
||||
@@ -81,6 +81,7 @@ private:
|
||||
qint64 _secondsInterval;
|
||||
bool _isBusy;
|
||||
bool _hasToken;
|
||||
bool _enforceHttps = false;
|
||||
};
|
||||
|
||||
} // namespace OCC
|
||||
|
||||
@@ -252,7 +252,7 @@ bool Folder::isBusy() const
|
||||
|
||||
bool Folder::isSyncRunning() const
|
||||
{
|
||||
return _engine->isSyncRunning() || _vfs->isHydrating();
|
||||
return _engine->isSyncRunning() || (_vfs && _vfs->isHydrating());
|
||||
}
|
||||
|
||||
QString Folder::remotePath() const
|
||||
|
||||
@@ -850,9 +850,6 @@ void FolderMan::slotEtagPollTimerTimeout()
|
||||
// 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 {
|
||||
const auto account = folder->accountState()->account();
|
||||
const auto capabilities = account->capabilities();
|
||||
const auto pushNotifications = account->pushNotifications();
|
||||
|
||||
return !pushNotificationsFilesReady(account.data());
|
||||
});
|
||||
|
||||
|
||||
@@ -51,6 +51,9 @@ void warnSystray()
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
SetDllDirectory(L"");
|
||||
#endif
|
||||
Q_INIT_RESOURCE(resources);
|
||||
Q_INIT_RESOURCE(theme);
|
||||
|
||||
|
||||
@@ -323,12 +323,29 @@ void OwncloudSetupWizard::slotConnectToOCUrl(const QString &url)
|
||||
qCInfo(lcWizard) << "Connect to url: " << url;
|
||||
AbstractCredentials *creds = _ocWizard->getCredentials();
|
||||
_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()
|
||||
@@ -465,7 +482,7 @@ void OwncloudSetupWizard::slotCreateLocalAndRemoteFolders(const QString &localFo
|
||||
*
|
||||
* 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);
|
||||
|
||||
@@ -76,9 +76,9 @@ signals:
|
||||
void openHelp();
|
||||
void shutdown();
|
||||
|
||||
Q_INVOKABLE void hideWindow();
|
||||
Q_INVOKABLE void showWindow();
|
||||
Q_INVOKABLE void openShareDialog(const QString &sharePath, const QString &localPath);
|
||||
void hideWindow();
|
||||
void showWindow();
|
||||
void openShareDialog(const QString &sharePath, const QString &localPath);
|
||||
|
||||
public slots:
|
||||
void slotNewUserSelected();
|
||||
|
||||
@@ -9,6 +9,9 @@ Item {
|
||||
|
||||
// label value
|
||||
property string text: ""
|
||||
|
||||
// font value
|
||||
property var font: label.font
|
||||
|
||||
// icon value
|
||||
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.name: qsTr("Account entry")
|
||||
|
||||
RowLayout {
|
||||
id: userLineLayout
|
||||
spacing: 0
|
||||
width: Style.currentAccountButtonWidth
|
||||
height: parent.height
|
||||
RowLayout {
|
||||
id: userLineLayout
|
||||
spacing: 0
|
||||
anchors.fill: parent
|
||||
|
||||
Button {
|
||||
id: accountButton
|
||||
Layout.preferredWidth: (userLineLayout.width * (5/6))
|
||||
Layout.preferredHeight: (userLineLayout.height)
|
||||
display: AbstractButton.IconOnly
|
||||
Button {
|
||||
id: accountButton
|
||||
Layout.preferredWidth: (userLineLayout.width * (5/6))
|
||||
Layout.preferredHeight: (userLineLayout.height)
|
||||
display: AbstractButton.IconOnly
|
||||
hoverEnabled: true
|
||||
flat: true
|
||||
|
||||
Accessible.role: Accessible.Button
|
||||
Accessible.name: qsTr("Switch to account") + " " + name
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
flat: true
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
onContainsMouseChanged: {
|
||||
accountStatusIndicatorBackground.color = (containsMouse ? "#f6f6f6" : "white")
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
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()
|
||||
onClicked: {
|
||||
if (!isCurrentUser) {
|
||||
UserModel.switchCurrentUser(id)
|
||||
} else {
|
||||
accountMenu.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import QtQml 2.1
|
||||
import QtQml 2.12
|
||||
import QtQml.Models 2.1
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Window 2.3
|
||||
@@ -19,7 +19,7 @@ Window {
|
||||
width: Systray.useNormalWindow ? Style.trayWindowHeight : Style.trayWindowWidth
|
||||
height: Style.trayWindowHeight
|
||||
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
|
||||
@@ -30,7 +30,7 @@ Window {
|
||||
onActiveChanged: {
|
||||
if (!Systray.useNormalWindow && !active) {
|
||||
hide();
|
||||
setClosed();
|
||||
Systray.setClosed();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,6 +362,9 @@ Window {
|
||||
spacing: 0
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
|
||||
Layout.leftMargin: Style.userStatusSpacing
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: parent.width
|
||||
|
||||
Label {
|
||||
id: currentAccountUser
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignBottom
|
||||
@@ -372,11 +375,14 @@ Window {
|
||||
font.pixelSize: Style.topLinePixelSize
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: currentUserStatus
|
||||
visible: UserModel.currentUser.isConnected &&
|
||||
UserModel.currentUser.serverHasUserStatus
|
||||
spacing: Style.accountLabelsSpacing
|
||||
width: parent.width
|
||||
|
||||
Label {
|
||||
id: emoji
|
||||
visible: UserModel.currentUser.statusEmoji !== ""
|
||||
@@ -386,6 +392,7 @@ Window {
|
||||
Label {
|
||||
id: message
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignBottom
|
||||
Layout.fillWidth: true
|
||||
visible: UserModel.currentUser.statusMessage !== ""
|
||||
width: Style.currentAccountLabelWidth
|
||||
text: UserModel.currentUser.statusMessage !== ""
|
||||
@@ -572,296 +579,11 @@ Window {
|
||||
|
||||
model: activityModel
|
||||
|
||||
delegate: RowLayout {
|
||||
id: activityItem
|
||||
|
||||
readonly property variant links: model.links
|
||||
|
||||
readonly property int itemIndex: model.index
|
||||
|
||||
width: parent.width
|
||||
delegate: ActivityItem {
|
||||
width: activityListView.width
|
||||
height: Style.trayWindowHeaderHeight
|
||||
spacing: 0
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
onClicked: activityModel.triggerDefaultAction(model.index)
|
||||
}
|
||||
|
||||
/*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
|
||||
}
|
||||
|
||||
@@ -104,15 +104,20 @@ bool OCUpdater::performUpdate()
|
||||
QString updateFile = settings.value(updateAvailableC).toString();
|
||||
if (!updateFile.isEmpty() && QFile(updateFile).exists()
|
||||
&& !updateSucceeded() /* Someone might have run the updater manually between restarts */) {
|
||||
const QString name = Theme::instance()->appNameGUI();
|
||||
if (QMessageBox::information(nullptr, tr("New %1 Update Ready").arg(name),
|
||||
tr("A new update for %1 is about to be installed. The updater may ask\n"
|
||||
"for additional privileges during the process.")
|
||||
.arg(name),
|
||||
QMessageBox::Ok)) {
|
||||
const auto messageBoxStartInstaller = new QMessageBox(QMessageBox::Information,
|
||||
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"
|
||||
"for additional privileges during the process.")
|
||||
.arg(Theme::instance()->appNameGUI()),
|
||||
QMessageBox::Ok,
|
||||
nullptr);
|
||||
|
||||
messageBoxStartInstaller->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
connect(messageBoxStartInstaller, &QMessageBox::finished, this, [this] {
|
||||
slotStartInstaller();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
messageBoxStartInstaller->open();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -216,6 +221,7 @@ void OCUpdater::slotStartInstaller()
|
||||
|
||||
QProcess::startDetached("powershell.exe", QStringList{"-Command", command});
|
||||
}
|
||||
qApp->quit();
|
||||
}
|
||||
|
||||
void OCUpdater::checkForUpdate()
|
||||
@@ -476,7 +482,6 @@ void NSISUpdater::showUpdateErrorDialog(const QString &targetVersion)
|
||||
// askagain: do nothing
|
||||
connect(retry, &QAbstractButton::clicked, this, [this]() {
|
||||
slotStartInstaller();
|
||||
qApp->quit();
|
||||
});
|
||||
connect(getupdate, &QAbstractButton::clicked, this, [this]() {
|
||||
slotOpenUpdateUrl();
|
||||
|
||||
@@ -16,12 +16,18 @@
|
||||
|
||||
#include "common/utility.h"
|
||||
#include "account.h"
|
||||
#include "creds/webflowcredentials.h"
|
||||
#include "networkjobs.h"
|
||||
#include "wizard/owncloudwizardcommon.h"
|
||||
#include "theme.h"
|
||||
#include "linklabel.h"
|
||||
|
||||
#include "QProgressIndicator.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QStringLiteral>
|
||||
#include <QJsonObject>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
Q_LOGGING_CATEGORY(lcFlow2AuthWidget, "nextcloud.gui.wizard.flow2authwidget", QtInfoMsg)
|
||||
|
||||
@@ -168,7 +168,7 @@ void OwncloudAdvancedSetupPage::initializePage()
|
||||
_ui.confCheckBoxExternal->setChecked(cfgFile.confirmExternalStorage());
|
||||
|
||||
fetchUserAvatar();
|
||||
fetchUserData();
|
||||
setUserInformation();
|
||||
|
||||
customizeStyle();
|
||||
|
||||
@@ -201,20 +201,9 @@ void OwncloudAdvancedSetupPage::fetchUserAvatar()
|
||||
avatarJob->start();
|
||||
}
|
||||
|
||||
void OwncloudAdvancedSetupPage::fetchUserData()
|
||||
void OwncloudAdvancedSetupPage::setUserInformation()
|
||||
{
|
||||
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();
|
||||
setServerAddressLabelUrl(serverUrl);
|
||||
const auto userName = account->davDisplayName();
|
||||
|
||||
@@ -82,7 +82,7 @@ private:
|
||||
void setResolutionGuiVisible(bool value);
|
||||
void setupResoultionWidget();
|
||||
void fetchUserAvatar();
|
||||
void fetchUserData();
|
||||
void setUserInformation();
|
||||
|
||||
// TODO: remove when UX decision is made
|
||||
void refreshVirtualFilesAvailibility(const QString &path);
|
||||
|
||||
@@ -52,6 +52,11 @@ public:
|
||||
|
||||
protected:
|
||||
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
|
||||
@@ -186,8 +191,10 @@ QWebEnginePage * WebEnginePage::createWindow(QWebEnginePage::WebWindowType type)
|
||||
return view;
|
||||
}
|
||||
|
||||
void WebEnginePage::setUrl(const QUrl &url) {
|
||||
void WebEnginePage::setUrl(const QUrl &url)
|
||||
{
|
||||
QWebEnginePage::setUrl(url);
|
||||
_enforceHttps = url.scheme() == QStringLiteral("https");
|
||||
}
|
||||
|
||||
bool WebEnginePage::certificateError(const QWebEngineCertificateError &certificateError)
|
||||
@@ -211,6 +218,18 @@ bool WebEnginePage::certificateError(const QWebEngineCertificateError &certifica
|
||||
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) {
|
||||
|
||||
}
|
||||
|
||||
@@ -59,7 +59,6 @@ const char app_password[] = "_app-password";
|
||||
Account::Account(QObject *parent)
|
||||
: QObject(parent)
|
||||
, _capabilities(QVariantMap())
|
||||
, _davPath(Theme::instance()->webDavPath())
|
||||
{
|
||||
qRegisterMetaType<AccountPtr>("AccountPtr");
|
||||
qRegisterMetaType<Account *>("Account*");
|
||||
@@ -85,18 +84,7 @@ Account::~Account() = default;
|
||||
|
||||
QString Account::davPath() const
|
||||
{
|
||||
if (capabilities().chunkingNg()) {
|
||||
// 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;
|
||||
return davPathBase() + QLatin1Char('/') + davUser() + QLatin1Char('/');
|
||||
}
|
||||
|
||||
void Account::setSharedThis(AccountPtr sharedThis)
|
||||
@@ -104,6 +92,11 @@ void Account::setSharedThis(AccountPtr sharedThis)
|
||||
_sharedThis = sharedThis.toWeakRef();
|
||||
}
|
||||
|
||||
QString Account::davPathBase()
|
||||
{
|
||||
return QStringLiteral("/remote.php/dav/files");
|
||||
}
|
||||
|
||||
AccountPtr Account::sharedFromThis()
|
||||
{
|
||||
return _sharedThis.toStrongRef();
|
||||
@@ -111,7 +104,7 @@ AccountPtr Account::sharedFromThis()
|
||||
|
||||
QString Account::davUser() const
|
||||
{
|
||||
return _davUser.isEmpty() ? _credentials->user() : _davUser;
|
||||
return _davUser.isEmpty() && _credentials ? _credentials->user() : _davUser;
|
||||
}
|
||||
|
||||
void Account::setDavUser(const QString &newDavUser)
|
||||
@@ -498,7 +491,27 @@ void Account::slotHandleSslErrors(QNetworkReply *reply, QList<QSslError> errors)
|
||||
|
||||
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()
|
||||
@@ -571,15 +584,6 @@ void Account::setServerVersion(const QString &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){
|
||||
if(_wroteAppPassword)
|
||||
return;
|
||||
|
||||
@@ -124,8 +124,6 @@ public:
|
||||
* @returns the (themeable) dav path for the account.
|
||||
*/
|
||||
QString davPath() const;
|
||||
void setDavPath(const QString &s) { _davPath = s; }
|
||||
void setNonShib(bool nonShib);
|
||||
|
||||
/** Returns webdav entry URL, based on url() */
|
||||
QUrl davUrl() const;
|
||||
@@ -296,6 +294,8 @@ private:
|
||||
Account(QObject *parent = nullptr);
|
||||
void setSharedThis(AccountPtr sharedThis);
|
||||
|
||||
static QString davPathBase();
|
||||
|
||||
QWeakPointer<Account> _sharedThis;
|
||||
QString _id;
|
||||
QString _davUser;
|
||||
@@ -329,7 +329,6 @@ private:
|
||||
|
||||
static QString _configFileName;
|
||||
|
||||
QString _davPath; // defaults to value from theme, might be overwritten in brandings
|
||||
ClientSideEncryption _e2e;
|
||||
|
||||
/// Used in RemoteWipe
|
||||
|
||||
@@ -1663,7 +1663,6 @@ bool EncryptionHelper::fileEncryption(const QByteArray &key, const QByteArray &i
|
||||
return false;
|
||||
}
|
||||
|
||||
qCDebug(lcCse) << "Encrypting " << data;
|
||||
if(!EVP_EncryptUpdate(ctx, unsignedData(out), &len, (unsigned char *)data.constData(), data.size())) {
|
||||
qCInfo(lcCse()) << "Could not encrypt";
|
||||
return false;
|
||||
|
||||
@@ -50,7 +50,6 @@
|
||||
#define DEFAULT_MAX_LOG_LINES 20000
|
||||
|
||||
namespace {
|
||||
static constexpr char allowChecksumValidationFailC[] = "allowChecksumValidationFail";
|
||||
static constexpr char showMainDialogAsNormalWindowC[] = "showMainDialogAsNormalWindow";
|
||||
}
|
||||
|
||||
@@ -892,11 +891,6 @@ void ConfigFile::setMoveToTrash(bool isChecked)
|
||||
setValue(moveToTrashC, isChecked);
|
||||
}
|
||||
|
||||
bool ConfigFile::allowChecksumValidationFail() const
|
||||
{
|
||||
return getValue(allowChecksumValidationFailC, {}, false).toBool();
|
||||
}
|
||||
|
||||
bool ConfigFile::showMainDialogAsNormalWindow() const {
|
||||
return getValue(showMainDialogAsNormalWindowC, {}, false).toBool();
|
||||
}
|
||||
|
||||
@@ -145,9 +145,6 @@ public:
|
||||
bool moveToTrash() const;
|
||||
void setMoveToTrash(bool);
|
||||
|
||||
/** should we allow checksum validation to fail? set to true to workaround corrupted checksums **/
|
||||
bool allowChecksumValidationFail() const;
|
||||
|
||||
bool showMainDialogAsNormalWindow() const;
|
||||
|
||||
static bool setConfDir(const QString &value);
|
||||
|
||||
@@ -403,7 +403,7 @@ bool LsColJob::finished()
|
||||
connect(&parser, &LsColXMLParser::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)) {
|
||||
// XML parse error
|
||||
emit finishedWithError(reply());
|
||||
@@ -926,7 +926,10 @@ void DetermineAuthTypeJob::start()
|
||||
oldFlowRequired->setIgnoreCredentialFailure(true);
|
||||
|
||||
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;
|
||||
} else {
|
||||
_resultGet = LoginFlowV2;
|
||||
|
||||
@@ -28,8 +28,6 @@
|
||||
#include "propagatedownloadencrypted.h"
|
||||
#include "common/vfs.h"
|
||||
|
||||
#include "configfile.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QFileInfo>
|
||||
@@ -40,11 +38,6 @@
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
constexpr quint16 numChecksumFailuresAllowed = 1;
|
||||
constexpr char *checksumFailureDbRecordPrefix = "ChecksumValidationFailed_";
|
||||
}
|
||||
|
||||
namespace OCC {
|
||||
|
||||
Q_LOGGING_CATEGORY(lcGetJob, "nextcloud.sync.networkjob.get", QtInfoMsg)
|
||||
@@ -830,38 +823,8 @@ void PropagateDownloadFile::slotGetFinished()
|
||||
validator->start(_tmpFile.fileName(), checksumHeader);
|
||||
}
|
||||
|
||||
void PropagateDownloadFile::slotChecksumFail(const QString &errMsg, const QByteArray &checksumType, const QByteArray &checksum, const QString &filePath)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PropagateDownloadFile::slotChecksumFail(const QString &errMsg)
|
||||
{
|
||||
FileSystem::remove(_tmpFile.fileName());
|
||||
propagator()->_anotherSyncNeeded = true;
|
||||
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
|
||||
static QString makeRecallFileName(const QString &fn)
|
||||
{
|
||||
@@ -989,9 +940,13 @@ void PropagateDownloadFile::transmissionChecksumValidated(const QByteArray &chec
|
||||
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)
|
||||
|
||||
@@ -202,14 +202,12 @@ private slots:
|
||||
|
||||
void abort(PropagatorJob::AbortType abortType) override;
|
||||
void slotDownloadProgress(qint64, qint64);
|
||||
void slotChecksumFail(const QString &errMsg, const QByteArray &checksumType, const QByteArray &checksum, const QString &filePath);
|
||||
void slotChecksumFail(const QString &errMsg);
|
||||
|
||||
private:
|
||||
void startAfterIsEncryptedIsChecked();
|
||||
void deleteExistingFolder();
|
||||
|
||||
void startContentChecksumCompute(const QByteArray &checksumType, const QString &path);
|
||||
|
||||
qint64 _resumeStart;
|
||||
qint64 _downloadProgress;
|
||||
QPointer<GETFileJob> _job;
|
||||
|
||||
@@ -76,6 +76,9 @@ void PushNotifications::closeWebSocket()
|
||||
_reconnectTimer->stop();
|
||||
}
|
||||
|
||||
disconnect(_webSocket, QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error), this, &PushNotifications::onWebSocketError);
|
||||
disconnect(_webSocket, &QWebSocket::sslErrors, this, &PushNotifications::onWebSocketSslErrors);
|
||||
|
||||
_webSocket->close();
|
||||
}
|
||||
|
||||
@@ -171,6 +174,8 @@ void PushNotifications::openWebSocket()
|
||||
const auto webSocketUrl = capabilities.pushNotificationsWebSocketUrl();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -799,55 +799,8 @@ void SyncEngine::slotPropagationFinished(bool success)
|
||||
_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) {
|
||||
_journal->setDataFingerprint(_discoveryPhase->_dataFingerprint);
|
||||
} else if (_discoveryPhase) {
|
||||
// TODO: Remove this when the file restoration problem is fixed for a user
|
||||
checkAndOverrideSetDataFingerprint();
|
||||
}
|
||||
|
||||
conflictRecordMaintenance();
|
||||
@@ -863,12 +816,6 @@ void SyncEngine::slotPropagationFinished(bool success)
|
||||
emit transmissionProgress(*_progressInfo);
|
||||
|
||||
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)
|
||||
|
||||
@@ -291,9 +291,6 @@ private:
|
||||
LocalDiscoveryStyle _lastLocalDiscoveryStyle = LocalDiscoveryStyle::FilesystemOnly;
|
||||
LocalDiscoveryStyle _localDiscoveryStyle = LocalDiscoveryStyle::FilesystemOnly;
|
||||
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;
|
||||
}
|
||||
|
||||
QString Theme::webDavPath() const
|
||||
{
|
||||
return QLatin1String("remote.php/webdav/");
|
||||
}
|
||||
|
||||
QString Theme::webDavPathNonShib() const
|
||||
{
|
||||
return QLatin1String("remote.php/nonshib-webdav/");
|
||||
}
|
||||
|
||||
bool Theme::linkSharing() const
|
||||
{
|
||||
return true;
|
||||
|
||||
@@ -337,15 +337,6 @@ public:
|
||||
*/
|
||||
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
|
||||
*
|
||||
|
||||
@@ -75,6 +75,7 @@ endif()
|
||||
|
||||
nextcloud_add_benchmark(LargeSync)
|
||||
|
||||
nextcloud_add_test(Account)
|
||||
nextcloud_add_test(FolderMan)
|
||||
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 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();
|
||||
if (path.startsWith(sRootUrl.path()))
|
||||
return path.mid(sRootUrl.path().length());
|
||||
if (path.startsWith(sRootUrl2.path()))
|
||||
return path.mid(sRootUrl2.path().length());
|
||||
if (path.startsWith(sUploadUrl.path()))
|
||||
return path.mid(sUploadUrl.path().length());
|
||||
if (path.startsWith(sRootUrl.path()))
|
||||
return path.mid(sRootUrl.path().length());
|
||||
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") {
|
||||
auto data = stream->readAll();
|
||||
if (data.contains("data-fingerprint")) {
|
||||
if (request.url().path().endsWith("webdav/"))
|
||||
if (request.url().path().endsWith("dav/files/admin/")) {
|
||||
++fingerprintRequests;
|
||||
else
|
||||
} else {
|
||||
fingerprintRequests = -10000; // fingerprint queried on incorrect path
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
|
||||
@@ -42,7 +42,8 @@ using namespace OCC::Utility;
|
||||
_successDown = true;
|
||||
}
|
||||
|
||||
void slotDownError(const QString &errMsg, const QByteArray&, const QByteArray&, const QString&) {
|
||||
void slotDownError(const QString &errMsg)
|
||||
{
|
||||
QCOMPARE(_expectedError, errMsg);
|
||||
_errorSeen = true;
|
||||
}
|
||||
|
||||
@@ -57,6 +57,12 @@ private slots:
|
||||
QVERIFY(folderman->addFolder(newAccountState.data(), folderDefinition(dirPath + "/sub/ownCloud1")));
|
||||
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
|
||||
// QString FolderMan::checkPathValidityForNewFolder(const QString& path, const QUrl &serverUrl, bool forNewDirectory)
|
||||
|
||||
@@ -91,7 +91,7 @@ private slots:
|
||||
auto oldLocalState = fakeFolder.currentLocalState();
|
||||
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' : ";
|
||||
fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *)
|
||||
-> QNetworkReply *{
|
||||
@@ -133,7 +133,7 @@ private slots:
|
||||
//
|
||||
// Check the same discovery error on the sync root
|
||||
//
|
||||
errorFolder = "webdav/";
|
||||
errorFolder = "dav/files/admin/";
|
||||
fatalErrorPrefix = "Server replied with an error while reading directory '' : ";
|
||||
errorSpy.clear();
|
||||
QVERIFY(!fakeFolder.syncOnce());
|
||||
|
||||
@@ -53,7 +53,7 @@ private slots:
|
||||
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:response>"
|
||||
"<d:href>/oc/remote.php/webdav/sharefolder/</d:href>"
|
||||
"<d:href>/oc/remote.php/dav/sharefolder/</d:href>"
|
||||
"<d:propstat>"
|
||||
"<d:prop>"
|
||||
"<oc:id>00004213ocobzus5kn6s</oc:id>"
|
||||
@@ -77,7 +77,7 @@ private slots:
|
||||
"</d:propstat>"
|
||||
"</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:prop>"
|
||||
"<oc:id>00004215ocobzus5kn6s</oc:id>"
|
||||
@@ -110,16 +110,16 @@ private slots:
|
||||
this, SLOT(slotFinishedSuccessfully()) );
|
||||
|
||||
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);
|
||||
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/webdav/sharefolder"));
|
||||
QVERIFY(_items.contains("/oc/remote.php/dav/sharefolder/quitte.pdf"));
|
||||
QVERIFY(_items.contains("/oc/remote.php/dav/sharefolder"));
|
||||
QVERIFY(_items.size() == 2 );
|
||||
|
||||
QVERIFY(_subdirs.contains("/oc/remote.php/webdav/sharefolder/"));
|
||||
QVERIFY(_subdirs.contains("/oc/remote.php/dav/sharefolder/"));
|
||||
QVERIFY(_subdirs.size() == 1);
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ private slots:
|
||||
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:response>"
|
||||
"<d:href>/oc/remote.php/webdav/sharefolder/</d:href>"
|
||||
"<d:href>/oc/remote.php/dav/sharefolder/</d:href>"
|
||||
"<d:propstat>"
|
||||
"<d:prop>"
|
||||
"<oc:id>00004213ocobzus5kn6s</oc:id>"
|
||||
@@ -151,7 +151,7 @@ private slots:
|
||||
"</d:propstat>"
|
||||
"</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:prop>"
|
||||
"<oc:id>00004215ocobzus5kn6s</oc:id>"
|
||||
@@ -184,7 +184,7 @@ private slots:
|
||||
this, SLOT(slotFinishedSuccessfully()) );
|
||||
|
||||
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(sizes.size() == 0 ); // No quota info in the XML
|
||||
@@ -207,7 +207,7 @@ private slots:
|
||||
this, SLOT(slotFinishedSuccessfully()) );
|
||||
|
||||
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(sizes.size() == 0 ); // No quota info in the XML
|
||||
@@ -229,7 +229,7 @@ private slots:
|
||||
this, SLOT(slotFinishedSuccessfully()) );
|
||||
|
||||
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(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'?>"
|
||||
"<d:multistatus xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\">"
|
||||
"<d:response>"
|
||||
"<d:href>/oc/remote.php/webdav/sharefolder/</d:href>"
|
||||
"<d:href>/oc/remote.php/dav/sharefolder/</d:href>"
|
||||
"<d:propstat>"
|
||||
"<d:prop>"
|
||||
"<oc:id>00004213ocobzus5kn6s</oc:id>"
|
||||
@@ -268,7 +268,7 @@ private slots:
|
||||
this, SLOT(slotFinishedSuccessfully()) );
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -276,7 +276,7 @@ private slots:
|
||||
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: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:prop>"
|
||||
"<oc:id>00004213ocobzus5kn6s</oc:id>"
|
||||
@@ -300,7 +300,7 @@ private slots:
|
||||
"</d:propstat>"
|
||||
"</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:prop>"
|
||||
"<oc:id>00004215ocobzus5kn6s</oc:id>"
|
||||
@@ -333,7 +333,7 @@ private slots:
|
||||
this, SLOT(slotFinishedSuccessfully()) );
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -398,7 +398,7 @@ private slots:
|
||||
this, SLOT(slotFinishedSuccessfully()) );
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -406,7 +406,7 @@ private slots:
|
||||
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:response>"
|
||||
"<d:href>/oc/remote.php/webdav/sharefolder/</d:href>"
|
||||
"<d:href>/oc/remote.php/dav/sharefolder/</d:href>"
|
||||
"<d:propstat>"
|
||||
"<d:prop>"
|
||||
"<oc:id>00004213ocobzus5kn6s</oc:id>"
|
||||
@@ -430,7 +430,7 @@ private slots:
|
||||
"</d:propstat>"
|
||||
"</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:prop>"
|
||||
"<oc:id>00004215ocobzus5kn6s</oc:id>"
|
||||
@@ -463,16 +463,16 @@ private slots:
|
||||
this, SLOT(slotFinishedSuccessfully()) );
|
||||
|
||||
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);
|
||||
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/webdav/sharefolder"));
|
||||
QVERIFY(_items.contains("/oc/remote.php/dav/sharefolder/quitte.pdf"));
|
||||
QVERIFY(_items.contains("/oc/remote.php/dav/sharefolder"));
|
||||
QVERIFY(_items.size() == 2 );
|
||||
|
||||
QVERIFY(_subdirs.contains("/oc/remote.php/webdav/sharefolder/"));
|
||||
QVERIFY(_subdirs.contains("/oc/remote.php/dav/sharefolder/"));
|
||||
QVERIFY(_subdirs.size() == 1);
|
||||
}
|
||||
|
||||
@@ -480,7 +480,7 @@ private slots:
|
||||
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:response>"
|
||||
"<d:href>/oc/remote.php/webdav/sharefolder/</d:href>"
|
||||
"<d:href>/oc/remote.php/dav/sharefolder/</d:href>"
|
||||
"<d:propstat>"
|
||||
"<d:prop>"
|
||||
"<oc:id>00004213ocobzus5kn6s</oc:id>"
|
||||
@@ -504,7 +504,7 @@ private slots:
|
||||
"</d:propstat>"
|
||||
"</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:prop>"
|
||||
"<oc:id>00004215ocobzus5kn6s</oc:id>"
|
||||
@@ -537,7 +537,7 @@ private slots:
|
||||
this, SLOT(slotFinishedSuccessfully()) );
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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