Merge branch 'bugfixing-slideshow' of https://github.com/nextcloud/desktop into bugfixing-slideshow
580
.drone.yml
|
@ -1,228 +1,366 @@
|
|||
clone:
|
||||
git:
|
||||
image: plugins/git
|
||||
tags: true
|
||||
depth: 1
|
||||
kind: pipeline
|
||||
name: qt-5.7
|
||||
|
||||
pipeline:
|
||||
qt-5.7:
|
||||
image: nextcloudci/client-5.7:client-5.7-4
|
||||
commands:
|
||||
# Install QtKeyChain
|
||||
- /bin/bash -c "
|
||||
source /opt/qt57/bin/qt57-env.sh &&
|
||||
cd /tmp &&
|
||||
git clone https://github.com/frankosterfeld/qtkeychain.git &&
|
||||
cd qtkeychain &&
|
||||
git checkout v0.9.1 &&
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake ../ &&
|
||||
make &&
|
||||
make install"
|
||||
# Build client
|
||||
- /bin/bash -c "
|
||||
source /opt/qt57/bin/qt57-env.sh &&
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
|
||||
make &&
|
||||
useradd -m -s /bin/bash test &&
|
||||
chown -R test:test . &&
|
||||
su -c 'ctest --output-on-failure' test"
|
||||
when:
|
||||
matrix:
|
||||
TESTS: qt-5.7
|
||||
qt-5.8:
|
||||
image: nextcloudci/client-5.8:client-5.8-4
|
||||
commands:
|
||||
# Install QtKeyChain
|
||||
- /bin/bash -c "
|
||||
source /opt/qt58/bin/qt58-env.sh &&
|
||||
cd /tmp &&
|
||||
git clone https://github.com/frankosterfeld/qtkeychain.git &&
|
||||
cd qtkeychain &&
|
||||
git checkout v0.9.1 &&
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake ../ &&
|
||||
make &&
|
||||
make install"
|
||||
# Build client
|
||||
- /bin/bash -c "
|
||||
source /opt/qt58/bin/qt58-env.sh &&
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
|
||||
make &&
|
||||
useradd -m -s /bin/bash test &&
|
||||
chown -R test:test . &&
|
||||
su -c 'ctest --output-on-failure' test"
|
||||
when:
|
||||
matrix:
|
||||
TESTS: qt-5.8
|
||||
qt-5.9:
|
||||
image: nextcloudci/client-5.9:client-5.9-5
|
||||
commands:
|
||||
# Install QtKeyChain
|
||||
- /bin/bash -c "
|
||||
source /opt/qt59/bin/qt59-env.sh &&
|
||||
cd /tmp &&
|
||||
git clone https://github.com/frankosterfeld/qtkeychain.git &&
|
||||
cd qtkeychain &&
|
||||
git checkout v0.9.1 &&
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake ../ &&
|
||||
make &&
|
||||
make install"
|
||||
# Build client
|
||||
- /bin/bash -c "
|
||||
source /opt/qt59/bin/qt59-env.sh &&
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
|
||||
make &&
|
||||
useradd -m -s /bin/bash test &&
|
||||
chown -R test:test . &&
|
||||
su -c 'ctest --output-on-failure' test"
|
||||
when:
|
||||
matrix:
|
||||
TESTS: qt-5.9
|
||||
qt-5.10:
|
||||
image: nextcloudci/client-5.10:client-5.10-3
|
||||
commands:
|
||||
# Install QtKeyChain
|
||||
- /bin/bash -c "
|
||||
export CC=gcc-7 &&
|
||||
export CXX=g++-7 &&
|
||||
source /opt/qt510/bin/qt510-env.sh &&
|
||||
cd /tmp &&
|
||||
git clone https://github.com/frankosterfeld/qtkeychain.git &&
|
||||
cd qtkeychain &&
|
||||
git checkout v0.9.1 &&
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake ../ &&
|
||||
make &&
|
||||
make install"
|
||||
# Build client
|
||||
- /bin/bash -c "
|
||||
export CC=gcc-7 &&
|
||||
export CXX=g++-7 &&
|
||||
source /opt/qt510/bin/qt510-env.sh &&
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
|
||||
make &&
|
||||
useradd -m -s /bin/bash test &&
|
||||
chown -R test:test . &&
|
||||
su -c 'ctest --output-on-failure' test"
|
||||
when:
|
||||
matrix:
|
||||
TESTS: qt-5.10
|
||||
qt-5.11:
|
||||
image: nextcloudci/client-5.11:client-5.11-3
|
||||
commands:
|
||||
# Install QtKeyChain
|
||||
- /bin/bash -c "
|
||||
export CC=gcc-7 &&
|
||||
export CXX=g++-7 &&
|
||||
source /opt/qt511/bin/qt511-env.sh &&
|
||||
cd /tmp &&
|
||||
git clone https://github.com/frankosterfeld/qtkeychain.git &&
|
||||
cd qtkeychain &&
|
||||
git checkout v0.9.1 &&
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake ../ &&
|
||||
make &&
|
||||
make install"
|
||||
# Build client
|
||||
- /bin/bash -c "
|
||||
export CC=gcc-7 &&
|
||||
export CXX=g++-7 &&
|
||||
source /opt/qt511/bin/qt511-env.sh &&
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
|
||||
make &&
|
||||
useradd -m -s /bin/bash test &&
|
||||
chown -R test:test . &&
|
||||
su -c 'ctest --output-on-failure' test"
|
||||
when:
|
||||
matrix:
|
||||
TESTS: qt-5.11
|
||||
steps:
|
||||
- name: build and test
|
||||
image: nextcloudci/client-5.7:client-5.7-4
|
||||
commands:
|
||||
# Install QtKeyChain
|
||||
- /bin/bash -c "
|
||||
source /opt/qt57/bin/qt57-env.sh &&
|
||||
cd /tmp &&
|
||||
git clone https://github.com/frankosterfeld/qtkeychain.git &&
|
||||
cd qtkeychain &&
|
||||
git checkout v0.9.1 &&
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake ../ &&
|
||||
make &&
|
||||
make install"
|
||||
# Build client
|
||||
- /bin/bash -c "
|
||||
source /opt/qt57/bin/qt57-env.sh &&
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
|
||||
make &&
|
||||
useradd -m -s /bin/bash test &&
|
||||
chown -R test:test . &&
|
||||
su -c 'ctest --output-on-failure' test"
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
event:
|
||||
- pull_request
|
||||
- push
|
||||
|
||||
qt-5.11-clang:
|
||||
image: nextcloudci/client-5.11:client-5.11-3
|
||||
commands:
|
||||
# Install QtKeyChain
|
||||
- /bin/bash -c "
|
||||
export CC=clang-6.0 &&
|
||||
export CXX=clang++-6.0 &&
|
||||
source /opt/qt511/bin/qt511-env.sh &&
|
||||
cd /tmp &&
|
||||
git clone https://github.com/frankosterfeld/qtkeychain.git &&
|
||||
cd qtkeychain &&
|
||||
git checkout v0.9.1 &&
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake ../ &&
|
||||
make &&
|
||||
make install"
|
||||
# Build client
|
||||
- /bin/bash -c "
|
||||
export CC=clang-6.0 &&
|
||||
export CXX=clang++-6.0 &&
|
||||
source /opt/qt511/bin/qt511-env.sh &&
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
|
||||
make &&
|
||||
useradd -m -s /bin/bash test &&
|
||||
chown -R test:test . &&
|
||||
su -c 'ctest --output-on-failure' test"
|
||||
when:
|
||||
matrix:
|
||||
TESTS: qt-5.11-clang
|
||||
---
|
||||
kind: pipeline
|
||||
name: qt-5.8
|
||||
|
||||
AppImage:
|
||||
image: nextcloudci/client-5.11:client-5.11-3
|
||||
commands:
|
||||
- /bin/bash -c "./admin/linux/build-appimage.sh"
|
||||
when:
|
||||
matrix:
|
||||
BUILD: AppImage
|
||||
steps:
|
||||
- name: build and test
|
||||
image: nextcloudci/client-5.8:client-5.8-4
|
||||
commands:
|
||||
# Install QtKeyChain
|
||||
- /bin/bash -c "
|
||||
source /opt/qt58/bin/qt58-env.sh &&
|
||||
cd /tmp &&
|
||||
git clone https://github.com/frankosterfeld/qtkeychain.git &&
|
||||
cd qtkeychain &&
|
||||
git checkout v0.9.1 &&
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake ../ &&
|
||||
make &&
|
||||
make install"
|
||||
# Build client
|
||||
- /bin/bash -c "
|
||||
source /opt/qt58/bin/qt58-env.sh &&
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
|
||||
make &&
|
||||
useradd -m -s /bin/bash test &&
|
||||
chown -R test:test . &&
|
||||
su -c 'ctest --output-on-failure' test"
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
event:
|
||||
- pull_request
|
||||
- push
|
||||
|
||||
Debian:
|
||||
image: nextcloudci/client-debian-ci:client-debian-ci-2
|
||||
commands:
|
||||
- /bin/bash -c "./admin/linux/debian/drone-build.sh"
|
||||
secrets: [ DEBIAN_SECRET_KEY, DEBIAN_SECRET_IV ]
|
||||
when:
|
||||
matrix:
|
||||
BUILD: Debian
|
||||
---
|
||||
kind: pipeline
|
||||
name: qt-5.9
|
||||
|
||||
documentation:
|
||||
image: nextcloudci/documentation:documentation-5
|
||||
commands:
|
||||
- cd doc
|
||||
- make html
|
||||
when:
|
||||
matrix:
|
||||
TESTS: documentation
|
||||
steps:
|
||||
- name: build and test
|
||||
image: nextcloudci/client-5.9:client-5.9-5
|
||||
commands:
|
||||
# Install QtKeyChain
|
||||
- /bin/bash -c "
|
||||
source /opt/qt59/bin/qt59-env.sh &&
|
||||
cd /tmp &&
|
||||
git clone https://github.com/frankosterfeld/qtkeychain.git &&
|
||||
cd qtkeychain &&
|
||||
git checkout v0.9.1 &&
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake ../ &&
|
||||
make &&
|
||||
make install"
|
||||
# Build client
|
||||
- /bin/bash -c "
|
||||
source /opt/qt59/bin/qt59-env.sh &&
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
|
||||
make &&
|
||||
useradd -m -s /bin/bash test &&
|
||||
chown -R test:test . &&
|
||||
su -c 'ctest --output-on-failure' test"
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
event:
|
||||
- pull_request
|
||||
- push
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- TESTS: qt-5.7
|
||||
- TESTS: qt-5.8
|
||||
- TESTS: qt-5.9
|
||||
- TESTS: qt-5.10
|
||||
- TESTS: qt-5.11
|
||||
- TESTS: qt-5.11-clang
|
||||
- BUILD: AppImage
|
||||
- BUILD: Debian
|
||||
- TESTS: documentation
|
||||
---
|
||||
kind: pipeline
|
||||
name: qt-5.10
|
||||
|
||||
branches: [ master, 2.* ]
|
||||
steps:
|
||||
- name: build and test
|
||||
image: nextcloudci/client-5.10:client-5.10-3
|
||||
commands:
|
||||
# Install QtKeyChain
|
||||
- /bin/bash -c "
|
||||
export CC=gcc-7 &&
|
||||
export CXX=g++-7 &&
|
||||
source /opt/qt510/bin/qt510-env.sh &&
|
||||
cd /tmp &&
|
||||
git clone https://github.com/frankosterfeld/qtkeychain.git &&
|
||||
cd qtkeychain &&
|
||||
git checkout v0.9.1 &&
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake ../ &&
|
||||
make &&
|
||||
make install"
|
||||
# Build client
|
||||
- /bin/bash -c "
|
||||
export CC=gcc-7 &&
|
||||
export CXX=g++-7 &&
|
||||
source /opt/qt510/bin/qt510-env.sh &&
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
|
||||
make &&
|
||||
useradd -m -s /bin/bash test &&
|
||||
chown -R test:test . &&
|
||||
su -c 'ctest --output-on-failure' test"
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
event:
|
||||
- pull_request
|
||||
- push
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: qt-5.11
|
||||
|
||||
steps:
|
||||
- name: build and test
|
||||
image: nextcloudci/client-5.11:client-5.11-3
|
||||
commands:
|
||||
# Install QtKeyChain
|
||||
- /bin/bash -c "
|
||||
export CC=gcc-7 &&
|
||||
export CXX=g++-7 &&
|
||||
source /opt/qt511/bin/qt511-env.sh &&
|
||||
cd /tmp &&
|
||||
git clone https://github.com/frankosterfeld/qtkeychain.git &&
|
||||
cd qtkeychain &&
|
||||
git checkout v0.9.1 &&
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake ../ &&
|
||||
make &&
|
||||
make install"
|
||||
# Build client
|
||||
- /bin/bash -c "
|
||||
export CC=gcc-7 &&
|
||||
export CXX=g++-7 &&
|
||||
source /opt/qt511/bin/qt511-env.sh &&
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
|
||||
make &&
|
||||
useradd -m -s /bin/bash test &&
|
||||
chown -R test:test . &&
|
||||
su -c 'ctest --output-on-failure' test"
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
event:
|
||||
- pull_request
|
||||
- push
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: qt-5.11-clang
|
||||
|
||||
steps:
|
||||
- name: build and test
|
||||
image: nextcloudci/client-5.11:client-5.11-3
|
||||
commands:
|
||||
# Install QtKeyChain
|
||||
- /bin/bash -c "
|
||||
export CC=clang-6.0 &&
|
||||
export CXX=clang++-6.0 &&
|
||||
source /opt/qt511/bin/qt511-env.sh &&
|
||||
cd /tmp &&
|
||||
git clone https://github.com/frankosterfeld/qtkeychain.git &&
|
||||
cd qtkeychain &&
|
||||
git checkout v0.9.1 &&
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake ../ &&
|
||||
make &&
|
||||
make install"
|
||||
# Build client
|
||||
- /bin/bash -c "
|
||||
export CC=clang-6.0 &&
|
||||
export CXX=clang++-6.0 &&
|
||||
source /opt/qt511/bin/qt511-env.sh &&
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
|
||||
make &&
|
||||
useradd -m -s /bin/bash test &&
|
||||
chown -R test:test . &&
|
||||
su -c 'ctest --output-on-failure' test"
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
event:
|
||||
- pull_request
|
||||
- push
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: qt-5.12
|
||||
|
||||
steps:
|
||||
- name: build and test
|
||||
image: nextcloudci/client-5.12:client-5.12-2
|
||||
commands:
|
||||
# Install QtKeyChain
|
||||
- /bin/bash -c "
|
||||
export CC=gcc-7 &&
|
||||
export CXX=g++-7 &&
|
||||
source /opt/qt512/bin/qt512-env.sh &&
|
||||
cd /tmp &&
|
||||
git clone https://github.com/frankosterfeld/qtkeychain.git &&
|
||||
cd qtkeychain &&
|
||||
git checkout v0.9.1 &&
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake ../ &&
|
||||
make &&
|
||||
make install"
|
||||
# Build client
|
||||
- /bin/bash -c "
|
||||
export CC=gcc-7 &&
|
||||
export CXX=g++-7 &&
|
||||
source /opt/qt512/bin/qt512-env.sh &&
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
|
||||
make &&
|
||||
useradd -m -s /bin/bash test &&
|
||||
chown -R test:test . &&
|
||||
su -c 'ctest --output-on-failure' test"
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
event:
|
||||
- pull_request
|
||||
- push
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: qt-5.12-clang
|
||||
|
||||
steps:
|
||||
- name: build and test
|
||||
image: nextcloudci/client-5.12:client-5.12-2
|
||||
commands:
|
||||
# Install QtKeyChain
|
||||
- /bin/bash -c "
|
||||
export CC=clang-6.0 &&
|
||||
export CXX=clang++-6.0 &&
|
||||
source /opt/qt512/bin/qt512-env.sh &&
|
||||
cd /tmp &&
|
||||
git clone https://github.com/frankosterfeld/qtkeychain.git &&
|
||||
cd qtkeychain &&
|
||||
git checkout v0.9.1 &&
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake ../ &&
|
||||
make &&
|
||||
make install"
|
||||
# Build client
|
||||
- /bin/bash -c "
|
||||
export CC=clang-6.0 &&
|
||||
export CXX=clang++-6.0 &&
|
||||
source /opt/qt512/bin/qt512-env.sh &&
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
|
||||
make &&
|
||||
useradd -m -s /bin/bash test &&
|
||||
chown -R test:test . &&
|
||||
su -c 'ctest --output-on-failure' test"
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
event:
|
||||
- pull_request
|
||||
- push
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: AppImage
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: nextcloudci/client-5.12:client-5.12-2
|
||||
commands:
|
||||
- /bin/bash -c "./admin/linux/build-appimage.sh"
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
event:
|
||||
- pull_request
|
||||
- push
|
||||
---
|
||||
kind: pipeline
|
||||
name: Debian
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: nextcloudci/client-debian-ci:client-debian-ci-2
|
||||
commands:
|
||||
- /bin/bash -c "./admin/linux/debian/drone-build.sh"
|
||||
environment:
|
||||
DEBIAN_SECRET_KEY:
|
||||
from_secret: DEBIAN_SECRET_KEY
|
||||
DEBIAN_SECRET_IV:
|
||||
from_secret: DEBIAN_SECRET_IV
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
event:
|
||||
- pull_request
|
||||
- push
|
||||
---
|
||||
kind: pipeline
|
||||
name: Documentation
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: nextcloudci/documentation:documentation-5
|
||||
commands:
|
||||
- cd doc
|
||||
- make html
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
event:
|
||||
- pull_request
|
||||
- push
|
||||
|
|
33
.github/issue_template.md
vendored
|
@ -1,9 +1,14 @@
|
|||
<!---
|
||||
Please try to only report a bug if it happens with the latest version
|
||||
The latest version can be seen by checking https://download.nextcloud.com/desktop/
|
||||
For support try our forums: https://help.nextcloud.com
|
||||
--->
|
||||
|
||||
<!--
|
||||
Dear user,
|
||||
Please understand that at the moment, we are very busy with customer issues
|
||||
and some high priority development work. A lot of issues are getting reported.
|
||||
Right now we can't keep up and timely respond to all of them.
|
||||
We're sorry for that and are expanding our team, if you're looking for a C++
|
||||
job or know somebody who is, please point them to https://nextcloud.com/jobs
|
||||
Don't forget that Github is not a support system or a place to ask for
|
||||
features but only a place to report verified bugs - see nextcloud.com/support
|
||||
for support options!
|
||||
-->
|
||||
|
||||
### Expected behaviour
|
||||
Tell us what should happen
|
||||
|
@ -18,6 +23,11 @@ Tell us what happens instead
|
|||
|
||||
### Client configuration
|
||||
Client version:
|
||||
<!---
|
||||
Please try to only report a bug if it happens with the latest version
|
||||
The latest version can be seen by checking https://download.nextcloud.com/desktop/
|
||||
For support try our forums: https://help.nextcloud.com
|
||||
--->
|
||||
|
||||
Operating system:
|
||||
|
||||
|
@ -34,15 +44,6 @@ Installation path of client:
|
|||
<!---
|
||||
Optional section. It depends on the issue.
|
||||
--->
|
||||
|
||||
Operating system:
|
||||
|
||||
Web server:
|
||||
|
||||
Database:
|
||||
|
||||
PHP version:
|
||||
|
||||
Nextcloud version:
|
||||
|
||||
Storage backend (external storage):
|
||||
|
@ -52,8 +53,6 @@ Storage backend (external storage):
|
|||
Please use Gist (https://gist.github.com/) or a similar code paster for longer
|
||||
logs.
|
||||
|
||||
```Template for output < 10 lines```
|
||||
|
||||
1. Client logfile: Output of `nextcloud --logwindow` or `nextcloud --logfile log.txt`
|
||||
(On Windows using `cmd.exe`, you might need to first `cd` into the Nextcloud directory)
|
||||
(See also https://docs.nextcloud.com/desktop/2.3/troubleshooting.html#log-files)
|
||||
|
|
10
.gitignore
vendored
|
@ -15,9 +15,18 @@ cscope.*
|
|||
tags
|
||||
t1.cfg
|
||||
|
||||
## Ignore Visual Studio Code config & environment files
|
||||
|
||||
.vs/
|
||||
.vscode/
|
||||
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
# CMake integration on VS2019+
|
||||
|
||||
CMakeSettings.json
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
|
@ -171,3 +180,4 @@ CPackConfig.cmake
|
|||
CPackOptions.cmake
|
||||
CPackSourceConfig.cmake
|
||||
|
||||
compile_commands.json
|
||||
|
|
201
.tx/nextcloud.client-desktop/ar_translation
Normal file
|
@ -0,0 +1,201 @@
|
|||
[Desktop Entry]
|
||||
Categories=Utility;X-SuSE-SyncUtility;
|
||||
Type=Application
|
||||
Exec=@APPLICATION_EXECUTABLE@
|
||||
Name=@APPLICATION_NAME@ desktop sync client
|
||||
Comment=@APPLICATION_NAME@ desktop synchronization client
|
||||
GenericName=Folder Sync
|
||||
Icon=@APPLICATION_ICON_NAME@
|
||||
Keywords=@APPLICATION_NAME@;syncing;file;sharing;
|
||||
X-GNOME-Autostart-Delay=3
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
GenericName[ar]=مزامنة المجلد
|
204
.tx/nextcloud.client-desktop/bg_translation
Normal file
|
@ -0,0 +1,204 @@
|
|||
[Desktop Entry]
|
||||
Categories=Utility;X-SuSE-SyncUtility;
|
||||
Type=Application
|
||||
Exec=@APPLICATION_EXECUTABLE@
|
||||
Name=@APPLICATION_NAME@ desktop sync client
|
||||
Comment=@APPLICATION_NAME@ desktop synchronization client
|
||||
GenericName=Folder Sync
|
||||
Icon=@APPLICATION_ICON_NAME@
|
||||
Keywords=@APPLICATION_NAME@;syncing;file;sharing;
|
||||
X-GNOME-Autostart-Delay=3
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
Icon[bg_BG]=@APPLICATION_ICON_NAME@
|
||||
Name[bg_BG]=@APPLICATION_NAME@ клиент за синхронизиране на десктоп
|
||||
Comment[bg_BG]=@APPLICATION_NAME@ клиент за синхронизиране на десктоп
|
||||
GenericName[bg_BG]=Синхронизиране на папка
|
|
@ -199,6 +199,6 @@ X-GNOME-Autostart-Delay=3
|
|||
|
||||
# Translations
|
||||
Icon[ca]=@APPLICATION_ICON_NAME@
|
||||
Name[ca]=@APPLICATION_NAME@ client de sincro d'escriptori
|
||||
Name[ca]=@APPLICATION_NAME@ client de sincronització d'escriptori
|
||||
Comment[ca]=@APPLICATION_NAME@ client de sincronització d'escriptori
|
||||
GenericName[ca]=Directori de sincronització
|
||||
|
|
|
@ -201,4 +201,4 @@ X-GNOME-Autostart-Delay=3
|
|||
Icon[da]=@APPLICATION_ICON_NAME@
|
||||
Name[da]=@APPLICATION_NAME@ Arbejdsstationsssynkroniseringsklient
|
||||
Comment[da]=@APPLICATION_NAME@ Arbejdsstationsssynkroniseringsklient
|
||||
GenericName[da]=Mappe sync
|
||||
GenericName[da]=Mappesynkronisering
|
||||
|
|
|
@ -198,6 +198,7 @@ X-GNOME-Autostart-Delay=3
|
|||
|
||||
|
||||
# Translations
|
||||
Name[hr]=sinkronizacija računala
|
||||
Comment[hr]=klijent za sinkronizaciju računala
|
||||
Icon[hr]=@APPLICATION_ICON_NAME@
|
||||
Name[hr]=@APPLICATION_NAME@ klijent za sink. računala
|
||||
Comment[hr]=@APPLICATION_NAME@ klijent za sinkronizaciju računala
|
||||
GenericName[hr]=Sinkronizacija mapa
|
||||
|
|
204
.tx/nextcloud.client-desktop/mk_translation
Normal file
|
@ -0,0 +1,204 @@
|
|||
[Desktop Entry]
|
||||
Categories=Utility;X-SuSE-SyncUtility;
|
||||
Type=Application
|
||||
Exec=@APPLICATION_EXECUTABLE@
|
||||
Name=@APPLICATION_NAME@ desktop sync client
|
||||
Comment=@APPLICATION_NAME@ desktop synchronization client
|
||||
GenericName=Folder Sync
|
||||
Icon=@APPLICATION_ICON_NAME@
|
||||
Keywords=@APPLICATION_NAME@;syncing;file;sharing;
|
||||
X-GNOME-Autostart-Delay=3
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
Icon[mk]=@APPLICATION_ICON_NAME@
|
||||
Name[mk]=@APPLICATION_NAME@ клиент за синхронизација на компјутер
|
||||
Comment[mk]=@APPLICATION_NAME@ клиент за синхронизација на компјутер
|
||||
GenericName[mk]=Папка за синхронизација
|
|
@ -201,4 +201,4 @@ X-GNOME-Autostart-Delay=3
|
|||
Icon[pt_BR]=@APPLICATION_ICON_NAME@
|
||||
Name[pt_BR]=@APPLICATION_NAME@ cliente de sincronização desktop
|
||||
Comment[pt_BR]=@APPLICATION_NAME@ cliente de sincronização desktop
|
||||
GenericName[pt_BR]=Sincronizar Pasta
|
||||
GenericName[pt_BR]=Sincronizar pasta
|
||||
|
|
|
@ -199,6 +199,6 @@ X-GNOME-Autostart-Delay=3
|
|||
|
||||
# Translations
|
||||
Icon[sk_SK]=@APPLICATION_ICON_NAME@
|
||||
Name[sk_SK]=@APPLICATION_NAME@ Synchronizačný klient pre PC
|
||||
Comment[sk_SK]=@APPLICATION_NAME@ Synchronizačný klient pre PC
|
||||
GenericName[sk_SK]=Synchnonizácia priečinka
|
||||
Name[sk_SK]=@APPLICATION_NAME@ synchronizačný klient pre PC
|
||||
Comment[sk_SK]=@APPLICATION_NAME@ synchronizačný klient pre PC
|
||||
GenericName[sk_SK]=Synchnonizácia priečinkov
|
||||
|
|
350
ChangeLog
|
@ -1,5 +1,138 @@
|
|||
ChangeLog
|
||||
=========
|
||||
2.5 Series ChangeLog
|
||||
====================
|
||||
|
||||
version 2.5.3 (release 2019-07-22)
|
||||
* Fix empty file wording in error log (small)
|
||||
* Add Qt-5.12 to CI
|
||||
* Fix a minor typo
|
||||
* Libcloudproviders: Add missing check for Qt5DBus
|
||||
* Fix several memory leaks in cloudproviders and add translation support
|
||||
* Share link fixing
|
||||
* New drone config
|
||||
* Uses configuraion to determine if it should show empty folder popup.
|
||||
* Simplify cmake command to make copy-pastable
|
||||
* Updated default remote poll to 5 seconds #1115
|
||||
* Fix memory leak with device pointer
|
||||
* Added a nice UI for the E2E-enabled account first connect
|
||||
* This should fix issue #1000
|
||||
* Adds parameter to retrieve shares with its reshares.
|
||||
* Fixed typo
|
||||
* Fixed typo in "certificate"
|
||||
* WebView: Properly handle usernames with spaces and plus signs in it
|
||||
* Add error category for http file lock error status 423.
|
||||
* Displays the uid_owner of a shared file.
|
||||
* Minor text change in the link to help in the tab 'General'.
|
||||
|
||||
version 2.5.2 (release 2019-04-11)
|
||||
* Handle spaces in username properly in login flow
|
||||
* Wizard: show an error message if there is no enough free space in the local folder
|
||||
* Removed whitespace from string
|
||||
* Do not add double slash to login flow url
|
||||
* Fix login flow with system proxy
|
||||
* Start with easier theming
|
||||
* Do not display dismissed notifications
|
||||
* Fixed l18n issue. Added space for separating string
|
||||
* Add invalid certiticate messagebox
|
||||
* Correct app passwords link
|
||||
* Be less verbose with logging
|
||||
* Fix typo in translation string
|
||||
* Add a command line option to launch the client in the background
|
||||
* Support Ubuntu Disco Dingo
|
||||
* Added missing Include
|
||||
* Make sure _profile and _page are deleted in the correct order
|
||||
* Fix KDEInstallDirs deprecation warnings
|
||||
* Removed Stylesheet
|
||||
|
||||
version 2.5.1 (release 2019-01-06)
|
||||
* Fixup the port in server notification URLs
|
||||
* GUI: let Clang-Tidy modernize nullptr & override usage
|
||||
* Improve the slide show
|
||||
* Libsync: let Clang-Tidy modernize nullptr & override usage
|
||||
* SettingsDialog: fix a little glitch in the account tool button size
|
||||
* SettingsDialog: tweak color aware icons
|
||||
* More verbose error and proper app name on configuration read error
|
||||
* Fix cmake build using WITH_PROVIDERS=OFF
|
||||
* Debian/Ubuntu target repository update
|
||||
* Change man page names and contents for nextcloud
|
||||
* Share dialog alignment
|
||||
* Fixed typo
|
||||
* Change link to docs for NC 15
|
||||
* Do not fetch activities if they are not enabled
|
||||
* Do not read system exclude list if user exclude is present
|
||||
* Fix the activity loop
|
||||
* Write the actual folder to the log
|
||||
* Fix appname for Nautilus integration script
|
||||
|
||||
version 2.5.0 (release 2018-11-14)
|
||||
* End to end encryption
|
||||
* New Web login flow
|
||||
* UI improvements: Notifications
|
||||
* UI improvements: refactoring of Activities
|
||||
* SyncJournal: Clear etag filter before sync
|
||||
* Partial local discovery: Fix scheduling logic
|
||||
* Sync hidden files by default
|
||||
* Larger Windows App Icon
|
||||
* Show a tray message when a folder watcher becomes unreliable #6119
|
||||
* Create symlinks for the small-letter application icon file names
|
||||
* In setup wizard put link to nextcloud installation
|
||||
* Web view scales vertically
|
||||
* Add a WebFlowCredentialsAccessManager
|
||||
* Mac Application Icon
|
||||
* Ensure GETFileJob notices finishing #6581
|
||||
* OAuth2: Try to refresh the token even if the credentials weren't ready.
|
||||
* Tray workarounds #6545
|
||||
* UpdateInfo: Remove unused code
|
||||
* OAuth: Remove the timeout
|
||||
* TestOAuth: Don't have global static QObject
|
||||
* Log: Adjust update/reconcile log verbosity
|
||||
* Reconcile: When detecting a local move, keep the local mtime
|
||||
* Wizard enhancement
|
||||
* FolderMan::checkPathValidityForNewFolder: make sure to work when fold…
|
||||
* Update: Report on readdir() errors #6610
|
||||
* Use encode()/decode() with Python 3 only
|
||||
* Sqlite: Update bundled version to 3.24.0
|
||||
* Do not require server replies to contain an mtime
|
||||
* Settings: Attempt to fix rename issue on old macOS
|
||||
* Support higher resolution theme icons
|
||||
* OAuth: Fix infinite loop when the refresh token is expired
|
||||
* Windows: Don't ignore files with FILE_ATTRIBUTE_TEMPORARY
|
||||
* Data-Fingerprint: Fix backup detection when fingerprint is empty
|
||||
* Nautilus: Fix GET_MENU_ITEMS with utf8 filenames #6643
|
||||
* Windows: Release handle/fd when file open fails #6699
|
||||
* SettingsDialog: Show the page for the newly created account
|
||||
* Updates submodule qtmacgoodies.
|
||||
* Fixes #665 Adds slot for confirmShare button.
|
||||
* Rename INSTALL to INSTALL.md for Preview :)
|
||||
* Add cmake temporary stuff
|
||||
* Inform user that configuration is not writable
|
||||
* Uses QByteArray to store private key.
|
||||
* Fix cmake command for linux in README too
|
||||
* Build fix: remove an unused QtSvg/QSvgRenderer include
|
||||
* Qtkeychain: 0.8.0 -> 0.9.1
|
||||
* Setup wizard: implement an animated and interactive slide show
|
||||
* Theming for general settings ui
|
||||
* Make the "Add Folder Sync Connection" button act like a button
|
||||
* Allow to use the login flow with a self signed certificate
|
||||
* Fix warning in ShareUserGroupWidget
|
||||
* Copy over config file to new location on windows
|
||||
* Update to translate strings
|
||||
* Migrate http auth to webflow
|
||||
* Margins
|
||||
* Qt 5.5 compatibility patch for Xenial
|
||||
* Fix cmake build of documentation
|
||||
* Use Nextcloud
|
||||
* Update isntaller background for OSX
|
||||
* Fix ActivityWidget palette
|
||||
* SettingsDialog: disable unnecessary wrapping for the about label
|
||||
* Added default scheme when server returns just a host
|
||||
* Removed explicit initialization; Fixed RAND_bytes not found
|
||||
* Actually open the activity view on a click for more info
|
||||
* Use a format that supports alpha channels for avatars
|
||||
* L10n. Added space for correct grammar.
|
||||
|
||||
|
||||
2.4 Series ChangeLog
|
||||
====================
|
||||
|
||||
version 2.4.1 (2017-02-xx)
|
||||
* Ignore files with file names that can't be encoded for the filesystem (#6287, #5676, #5719)
|
||||
|
@ -114,6 +247,10 @@ version 2.4.0 (2017-12-21)
|
|||
* Compile with stack-smashing protection
|
||||
* Updater: Rudimentary support for beta channel (#6048)
|
||||
|
||||
|
||||
2.3 Series ChangeLog
|
||||
====================
|
||||
|
||||
version 2.3.4 (2017-11-02)
|
||||
* Checksums: Use addData function to avoid endless loop CPU load issues with Office files
|
||||
* Packaging: Require ZLIB
|
||||
|
@ -184,6 +321,10 @@ version 2.3.0 (2017-03-03)
|
|||
* Improved documentation
|
||||
* Crash fixes
|
||||
|
||||
|
||||
2.2 Series ChangeLog
|
||||
====================
|
||||
|
||||
version 2.2.4 (release 2016-09-27)
|
||||
* Dolphin Plugin: Use the Application name for the socket path (#5172)
|
||||
* SyncEngine: Fix renaming of folder when file are changed (#5195)
|
||||
|
@ -248,6 +389,10 @@ version 2.2.0 (release 2016-05-12)
|
|||
* Update of QtKeyChain to support Windows credential store
|
||||
* Packaging of dolphin overlay icon module for bleeding edge distros
|
||||
|
||||
|
||||
2.1 Series ChangeLog
|
||||
====================
|
||||
|
||||
version 2.1.1 (release 2016-02-10)
|
||||
* UI improvements for HiDPI screens, error messages, RTL languages
|
||||
* Fix occurences of "Connection Closed" when a new unauthenticated TCP socket is used
|
||||
|
@ -281,8 +426,8 @@ version 2.1 (release 2015-12-03)
|
|||
* Improved reconnecting after network change/disconnect (#4167 #3969 ...)
|
||||
* Improved performance in Windows file system discovery
|
||||
* Removed libneon-based propagator. As a consequence, The client can no
|
||||
longer provide bandwith limiting on Linux-distributions where it is
|
||||
using Qt < 5.4
|
||||
* longer provide bandwith limiting on Linux-distributions where it is
|
||||
* using Qt < 5.4
|
||||
* Performance improvements in the logging functions
|
||||
* Ensured that local disk space problems are handled gracefully (#2939)
|
||||
* Improved handling of checksums: transport validation, db (#3735)
|
||||
|
@ -318,6 +463,10 @@ version 2.1 (release 2015-12-03)
|
|||
* Organized patches to our base Qt version into admin/qt/patches
|
||||
* Plus: A lot of unmentioned improvements and fixes
|
||||
|
||||
|
||||
2.0 Series ChangeLog
|
||||
====================
|
||||
|
||||
version 2.0.2 (release 2015-10-21)
|
||||
* csync_file_stat_s: Save a bit of memory
|
||||
* Shibboleth: Add our base user agent to WebKit
|
||||
|
@ -393,6 +542,10 @@ version 2.0.0 (release 2015-08-25)
|
|||
* Bandwidth Throttling: Provide automatic limit setting for downloads (#3084)
|
||||
* Systray: Workaround for issue with Qt 5.5.0 #3656
|
||||
|
||||
|
||||
1.8 Series ChangeLog
|
||||
====================
|
||||
|
||||
version 1.8.4 (release 2015-07-13)
|
||||
* Release to ship a security release of openSSL. No source changes of the ownCloud Client code.
|
||||
|
||||
|
@ -402,7 +555,7 @@ version 1.8.3 (release 2015-06-23)
|
|||
* Ignores: Force a remote discovery after ignore list change (#3172)
|
||||
* Shibboleth: Avoid crash by letting the webview use its own QNAM (#3359)
|
||||
* System Ignores: Removed *.tmp from system ignore again. If a user
|
||||
wants to ignore *.tmp, it needs to be added to the user ignore list.
|
||||
* wants to ignore *.tmp, it needs to be added to the user ignore list.
|
||||
|
||||
version 1.8.2 (release 2015-06-08)
|
||||
* Improve reporting of server error messages (#3220)
|
||||
|
@ -415,16 +568,16 @@ version 1.8.2 (release 2015-06-08)
|
|||
* HTTP: Add the branding name to the UserAgent string
|
||||
* ConnectonValidator: Always run with new credentials (#3266)
|
||||
* Recall Feature: Admins can trigger an upload of a file from
|
||||
client to server again (#3246)
|
||||
* client to server again (#3246)
|
||||
* Propagator: Add 'Content-Length: 0' header to MKCOL request (#3256)
|
||||
* Switch on checksum verification through branding or config
|
||||
* Add ability for checksum verification of up and download
|
||||
* Fix opening external links for some labels (#3135)
|
||||
* AccountState: Run only a single validator, allow error message
|
||||
overriding (#3236, #3153)
|
||||
* overriding (#3236, #3153)
|
||||
* SyncJournalDB: Minor fixes and simplificatons
|
||||
* SyncEngine: Force re-read of folder Etags for upgrades from
|
||||
1.8.0 and 1.8.1
|
||||
* 1.8.0 and 1.8.1
|
||||
* Propagator: Limit length of temporary file name (#2789)
|
||||
* ShareDialog: Password ui fixes (#3189)
|
||||
* Fix startup hang by removing QSettings lock file (#3175)
|
||||
|
@ -445,12 +598,12 @@ version 1.8.2 (release 2015-06-08)
|
|||
version 1.8.1 (release 2015-05-07)
|
||||
* Make "operation canceled" error a soft error
|
||||
* Do not throw an error for files that are scheduled to be removed,
|
||||
but can not be found on the server. #2919
|
||||
* but can not be found on the server. #2919
|
||||
* Windows: Reset QNAM to proper function after hibernation. #2899 #2895 #2973
|
||||
* Fix argument verification of --confdir #2453
|
||||
* Fix a crash when accessing a dangling UploadDevice pointer #2984
|
||||
* Add-folder wizard: Make sure there is a scrollbar if folder names
|
||||
are too long #2962
|
||||
* are too long #2962
|
||||
* Add-folder Wizard: Select the newly created folder
|
||||
* Activity: Correctly restore column sizes #3005
|
||||
* SSL Button: do not crash on empty certificate chain
|
||||
|
@ -458,8 +611,8 @@ version 1.8.1 (release 2015-05-07)
|
|||
* Lookup system proxy async to avoid hangs #2993 #2802
|
||||
* ShareDialog: Some GUI refinements
|
||||
* ShareDialog: On creation of a share always retrieve the share
|
||||
This makes sure that if a default expiration date is set this is reflected
|
||||
in the dialog. #2889
|
||||
* This makes sure that if a default expiration date is set this is reflected
|
||||
* in the dialog. #2889
|
||||
* ShareDialog: Only show share dialog if we are connected.
|
||||
* HttpCreds: Fill pw dialog with previous password. #2848 #2879
|
||||
* HttpCreds: Delete password from old location. #2186
|
||||
|
@ -468,7 +621,7 @@ version 1.8.1 (release 2015-05-07)
|
|||
* ProtocolWidget: Always add seconds to the DateTime locale. #2535
|
||||
* Updater: Give context as to which app is about to be updated #3040
|
||||
* Windows: Add version information for owncloud.exe. This should help us know
|
||||
what version or build number a crash report was generated with.
|
||||
* what version or build number a crash report was generated with.
|
||||
* Fix a crash on shutdown in ~SocketApi #3057
|
||||
* SyncEngine: Show more timing measurements #3064
|
||||
* Discovery: Add warning if returned etag is 0
|
||||
|
@ -491,8 +644,8 @@ version 1.8.1 (release 2015-05-07)
|
|||
version 1.8.0 (release 2015-03-17)
|
||||
* Mac OS: HIDPI support
|
||||
* Support Sharing from desktop: Added a share dialog that can be
|
||||
opened by context menu in the file managers (Win, Mac, Nautilus)
|
||||
Supports public links with password enforcement
|
||||
* opened by context menu in the file managers (Win, Mac, Nautilus)
|
||||
* Supports public links with password enforcement
|
||||
* Enhanced usage of parallel HTTP requests for ownCloud 8 servers
|
||||
* Renamed github repository from mirall to client.
|
||||
* Mac OS: Use native notification support
|
||||
|
@ -505,7 +658,7 @@ version 1.8.0 (release 2015-03-17)
|
|||
* Build with Qt 5.4
|
||||
* Dropped libneon dependency if Qt 5.4 is available
|
||||
* Keep files open very short, that avoid lock problems on Windows
|
||||
especially with office software but also others.
|
||||
* especially with office software but also others.
|
||||
* Merged some NetBSD patches
|
||||
* Selective sync support for owncloudcmd
|
||||
* Reorganize the source repository
|
||||
|
@ -514,13 +667,17 @@ version 1.8.0 (release 2015-03-17)
|
|||
* A huge amount of bug fixes in all areas of the client.
|
||||
* almost 700 commits since 1.7.1
|
||||
|
||||
|
||||
1.7 Series ChangeLog
|
||||
====================
|
||||
|
||||
version 1.7.1 (release 2014-12-18)
|
||||
* Documentation fixes and updates
|
||||
* Nautilus Python plugin fixed for Python 3
|
||||
* GUI wording fixes plus improved log messages
|
||||
* Fix hidning of the database files in the sync directories
|
||||
* Compare http download size with the header value to avoid broken
|
||||
downloads, bug #2528
|
||||
* downloads, bug #2528
|
||||
* Avoid initial ETag fetch job at startup, which is not needed.
|
||||
* Add chunk size http header to PUT requests
|
||||
* Fixed deteteCookie method of our CookieJar, fix for Shibboleth
|
||||
|
@ -543,21 +700,20 @@ version 1.7.1 (release 2014-12-18)
|
|||
* Win32: Improve reliability of Installer, fix removal of Shell Extensions
|
||||
|
||||
version 1.7.0 (release 2014-11-07)
|
||||
|
||||
* oC7 Sharing: Handle new sharing options of ownCloud 7 correctly.
|
||||
* Added Selective sync: Ability to unselect server folders which are
|
||||
excluded from syncing, plus GUI and setup GUI
|
||||
* excluded from syncing, plus GUI and setup GUI
|
||||
* Added overlay icons for Windows Explorer, Mac OS Finder and GNOME Nautilus.
|
||||
Information is provided by the client via a local socket / named pipe API
|
||||
which provides information about the sync status of files.
|
||||
* Information is provided by the client via a local socket / named pipe API
|
||||
* which provides information about the sync status of files.
|
||||
* Improved local change detection: consider file size, detect files
|
||||
with ongoing changes and do not upload immediately
|
||||
* with ongoing changes and do not upload immediately
|
||||
* Improved HTTP request timeout handler: all successful requests reset
|
||||
the timeout counter
|
||||
* the timeout counter
|
||||
* Improvements for syncing command line tool: netrc support, improved
|
||||
SSL support, non interactive mode
|
||||
* SSL support, non interactive mode
|
||||
* Permission system: ownCloud 7 delivers file and folder permissions,
|
||||
added ability to deal with it for shared folders and more.
|
||||
* added ability to deal with it for shared folders and more.
|
||||
* Ignore handling: Do not recurse into ignored or excluded directories
|
||||
* Major sync journal database improvements for more stability and performance
|
||||
* New library interface to sqlite3
|
||||
|
@ -566,35 +722,40 @@ version 1.7.0 (release 2014-11-07)
|
|||
* Improved logging: more useful meta info, removed noise
|
||||
* Updated to latest Qt5 versions on Windows and OS X
|
||||
* Fixed data loss when renaming a download temporary fails and there was
|
||||
a conflict at the same time.
|
||||
* a conflict at the same time.
|
||||
* Fixed missing warnings about reusing a sync folder when the back button
|
||||
was used in the advanced folder setup wizard.
|
||||
* was used in the advanced folder setup wizard.
|
||||
* The 'Retry Sync' button now also restarts all downloads.
|
||||
* Clean up temporary downloads and some extra database files when wiping a
|
||||
folder.
|
||||
* folder.
|
||||
* OS X: Sparkle update to provide pkg format properly
|
||||
* OS X: Change distribution format from dmg to pkg with new installer.
|
||||
* Windows: Fix handling of filenames with trailing dot or space
|
||||
* Windows: Don't use the wrong way to get file mtimes in the legacy propagator.
|
||||
|
||||
|
||||
|
||||
1.6 Series ChangeLog
|
||||
====================
|
||||
|
||||
version 1.6.4 (release 2014-10-22)
|
||||
* Fix startup logic, fixes bug #1989
|
||||
* Fix raise dialog on X11
|
||||
* Win32: fix overflow when computing the size of file > 4GiB
|
||||
* Use a fixed function to get files modification time, the
|
||||
original one was broken for certain timezone issues, see
|
||||
core bug #9781 for details
|
||||
* original one was broken for certain timezone issues, see
|
||||
* core bug #9781 for details
|
||||
* Added some missing copyright headers
|
||||
* Avoid data corruption due to wrong error handling, bug #2280
|
||||
* Do improved request timeout handling to reduce the number of
|
||||
timed out jobs, bug #2155
|
||||
version 1.6.3 (release 2014-09-03)
|
||||
* timed out jobs, bug #2155
|
||||
* version 1.6.3 (release 2014-09-03)
|
||||
* Fixed updater on OS X
|
||||
* Fixed memory leak in SSL button that could lead to quick memory draining
|
||||
* Fixed upload problem with files >4 GB
|
||||
* MacOSX, Linux: Bring Settings window to front properly
|
||||
* Branded clients: If no configuration is detected, try to import the data
|
||||
from a previously configured community edition.
|
||||
* from a previously configured community edition.
|
||||
|
||||
version 1.6.2 (release 2014-07-28 )
|
||||
* Limit the HTTP buffer size when downloading to limit memory consumption.
|
||||
|
@ -602,7 +763,7 @@ version 1.6.2 (release 2014-07-28 )
|
|||
* Fix local file name clash detection for MacOSX.
|
||||
* Limit maximum wait time to ten seconds in network limiting.
|
||||
* Fix data corruption while trying to resume and the server does
|
||||
not support it.
|
||||
* not support it.
|
||||
* HTTP Credentials: Read password from legacy place if not found.
|
||||
* Shibboleth: Fix the waiting curser that would not disapear (#1915)
|
||||
* Limit memory usage to avoid mem wasting and crashes
|
||||
|
@ -616,18 +777,18 @@ version 1.6.1 (release 2014-06-26 )
|
|||
* Fix openSSL problems for windows deployment
|
||||
* Fix syncing a folder with '#' in the name
|
||||
* Fix #1845: do not update parent directory etag before sub
|
||||
directories are removed
|
||||
* directories are removed
|
||||
* Fix reappearing directories if dirs are removed during its
|
||||
upload
|
||||
* upload
|
||||
* Fix app version in settings dialog, General tab
|
||||
* Fix crash in FolderWizard when going offline
|
||||
* Shibboleth fixes
|
||||
* More specific error messages (file remove during upload, open
|
||||
local sync file)
|
||||
* local sync file)
|
||||
* Use QSet rather than QHash in SyncEngine (save memory)
|
||||
* Fix some memory leaks
|
||||
* Fix some thread race problems, ie. wait for neon thread to finish
|
||||
before the propagator is shut down
|
||||
* before the propagator is shut down
|
||||
* Fix a lot of issues and warnings found by Coverity
|
||||
* Fix Mac some settings dialog problems
|
||||
|
||||
|
@ -650,16 +811,16 @@ version 1.6.0 (release 2014-05-30 )
|
|||
* Introduce a general timeout of 300s for network operations
|
||||
* Improve error handling, blacklisting
|
||||
* Job-based change propagation, enables faster parallel up/downloads
|
||||
(right now only if no bandwidth limit is set and no proxy is used)
|
||||
* (right now only if no bandwidth limit is set and no proxy is used)
|
||||
* Significantly reduced CPU load when checking for local and remote changes
|
||||
* Speed up file stat code on Windows
|
||||
* Enforce Qt5 for Windows and Mac OS X builds
|
||||
* Improved owncloudcmd: SSL support, documentation
|
||||
* Added advanced logging of operations (file .???.log in sync
|
||||
directory)
|
||||
* directory)
|
||||
* Avoid creating a temporary copy of the sync database (.ctmp)
|
||||
* Enable support for TLS 1.2 negotiation on platforms that use
|
||||
Qt 5.2 or later
|
||||
* Qt 5.2 or later
|
||||
* Forward server exception messages to client error messages
|
||||
* Mac OS X: Support Notification Center in OS X 10.8+
|
||||
* Mac OS X: Use native settings dialog
|
||||
|
@ -668,11 +829,15 @@ version 1.6.0 (release 2014-05-30 )
|
|||
* Remove vio abstraction in csync
|
||||
* Avoid data loss when a client file system is not case sensitive
|
||||
|
||||
|
||||
1.5 Series ChangeLog
|
||||
====================
|
||||
|
||||
version 1.5.3 (release 2014-03-10 )
|
||||
* Fix usage of proxies after first sync run (#1502, #1524, #1459, #1521)
|
||||
* Do not wipe the credentials from config for reconnect (#1499, #1503)
|
||||
* Do not erase the full account config if an old version of the client stored
|
||||
the password (related to above)
|
||||
* the password (related to above)
|
||||
* Fix layout of the network tab (fixes #1491)
|
||||
* Handle authentication requests by a Shibboleth IdP
|
||||
* Shibboleth: If no connection is available, don't open the login window
|
||||
|
@ -701,34 +866,34 @@ version 1.5.2 (release 2014-02-26 )
|
|||
|
||||
version 1.5.1 (release 2014-02-13 )
|
||||
* Added an auto updater that updates the client if a
|
||||
more recent version was found automatically (Windows, Mac OS X)
|
||||
* more recent version was found automatically (Windows, Mac OS X)
|
||||
* Added a button to the account dialog that gives information
|
||||
about the encryption layer used for communication, plus a
|
||||
certificate information widget
|
||||
* about the encryption layer used for communication, plus a
|
||||
* certificate information widget
|
||||
* Preserve the permission settings of local files rather than
|
||||
setting them to a default (Bug #820)
|
||||
* setting them to a default (Bug #820)
|
||||
* Handle windows lnk files correctly (Bug #1307)
|
||||
* Detect removes and renames in read only shares and
|
||||
restore the gone away files. (Bug #1386)
|
||||
* restore the gone away files. (Bug #1386)
|
||||
* Fixes sign in/sign out and password dialog. (Bug #1353)
|
||||
* Fixed error messages (Bug #1394)
|
||||
* Lots of fixes for building with Qt5
|
||||
* Changes to network limits are now also applied during a
|
||||
sync run
|
||||
* sync run
|
||||
* Fixed mem leak after via valgrind on Mac
|
||||
* Imported the ocsync library into miralls repository.
|
||||
Adopted all build systems and packaging to that.
|
||||
* Adopted all build systems and packaging to that.
|
||||
* Introduce a new linux packaging scheme following the
|
||||
debian upstream scheme
|
||||
* debian upstream scheme
|
||||
* Use a refactored Linux file system watcher based on
|
||||
inotify, incl. unit tests
|
||||
* inotify, incl. unit tests
|
||||
* Wizard: Gracefully fall back to HTTP if HTTPS connection
|
||||
fails, issuing a warning
|
||||
* fails, issuing a warning
|
||||
* Fixed translation misses in the propagator
|
||||
* Fixes in proxy configuration
|
||||
* Fixes in sync journal handling
|
||||
* Fix the upload progress if the local source is still
|
||||
changing when the upload begins.
|
||||
* changing when the upload begins.
|
||||
* Add proxy support to owncloud commandline client
|
||||
* NSIS fixes
|
||||
* A lot of other fixes and minor improvements
|
||||
|
@ -765,6 +930,10 @@ version 1.5.0 (release 2013-12-12 ), csync 0.91.4 required
|
|||
* Windows: Fix rename of temporary files
|
||||
* Windows: Fix move file operation
|
||||
|
||||
|
||||
1.4 Series ChangeLog
|
||||
====================
|
||||
|
||||
version 1.4.2 (release 2013-10-18 ), csync 0.90.4 required
|
||||
* Do not show the warning icon in the tray (#944)
|
||||
* Fix manual proxy support when switching (#1016)
|
||||
|
@ -780,12 +949,11 @@ version 1.4.2 (release 2013-10-18 ), csync 0.90.4 required
|
|||
* Progress: Show number of deletes.
|
||||
|
||||
version 1.4.1 (release 2013-09-24 ), csync 0.90.1 required
|
||||
|
||||
* Translation and documentation fixes.
|
||||
* Fixed error display in settings/status dialog, displays multi
|
||||
line error messages now correctly.
|
||||
* line error messages now correctly.
|
||||
* Wait up to 30 secs before complaining about missing systray
|
||||
Fixes bug #949
|
||||
* Fixes bug #949
|
||||
* Fixed utf8 issues with basic auth authentication, fixes bug #941
|
||||
* Fixed remote folder selector, avoid recursive syncing, fixes bug #962
|
||||
* Handle and display network problems at startup correctly.
|
||||
|
@ -802,7 +970,6 @@ version 1.4.1 (release 2013-09-24 ), csync 0.90.1 required
|
|||
* Various minor code fixes
|
||||
|
||||
version 1.4.0 (release 2013-09-04 ), csync 0.90.0 required
|
||||
|
||||
* New Scheduler: Only sync when there are actual changes in the server
|
||||
* Add a Settings Dialog, move Proxy Settings there
|
||||
* Transform folder Status Dialog into Account Settings, provide feedback via context menu
|
||||
|
@ -813,7 +980,7 @@ version 1.4.0 (release 2013-09-04 ), csync 0.90.0 required
|
|||
* Move ability to switch to mono icons from a switch to a Settings option
|
||||
* Add "Launch on System Startup" GUI option
|
||||
* Add "Show Desktop Nofications"GUI option (enabled by default)
|
||||
top optionally disable sync notifications
|
||||
* top optionally disable sync notifications
|
||||
* Add Help item, pointing to online reference
|
||||
* Implement graphical selection of remote folders in FolderWizard
|
||||
* Allow custom ignore patterns
|
||||
|
@ -832,11 +999,14 @@ version 1.4.0 (release 2013-09-04 ), csync 0.90.0 required
|
|||
* Require Qt 4.7
|
||||
* Known issue: Under certain conditions, a file will only get uploaded after up to five minutes
|
||||
|
||||
version 1.3.0 (release 2013-06-25 ), csync 0.80.0 required
|
||||
|
||||
1.3 Series ChangeLog
|
||||
====================
|
||||
|
||||
version 1.3.0 (release 2013-06-25 ), csync 0.80.0 required
|
||||
* Default proxy port to 8080
|
||||
* Don't lose proxy settings when changing passwords
|
||||
* Support SOCKS5 proxy (useful in combination with ssh *D)
|
||||
* Support SOCKS5 proxy (useful in combination with ssh* *D)
|
||||
* Propagate proxy changes to csync at runtime
|
||||
* Improve proxy wizard
|
||||
* Display proxy errors
|
||||
|
@ -852,7 +1022,7 @@ version 1.3.0 (release 2013-06-25 ), csync 0.80.0 required
|
|||
* Remove journal when reusing a directory that used to have a journal before
|
||||
* Visual clean up of status dialog items
|
||||
* Wizard: When changing the URL or user name, allow the user to push his data
|
||||
to the new location or wipe the folder and start from scratch
|
||||
* to the new location or wipe the folder and start from scratch
|
||||
* Wizard: Make setting a custom folder as a sync target work again
|
||||
* Fix application icon
|
||||
* User-Agent now contains "Mozilla/5.0" and the Platform name (for firewall/proxy compat)
|
||||
|
@ -860,6 +1030,10 @@ version 1.3.0 (release 2013-06-25 ), csync 0.80.0 required
|
|||
* New setup wizard, defaulting to root syncing (only for new setups)
|
||||
* Improved thread stop/termination
|
||||
|
||||
|
||||
1.2 Series ChangeLog
|
||||
====================
|
||||
|
||||
version 1.2.5 (release 2013-04-23 ), csync 0.70.7 required
|
||||
* [Fixes] NSIS installer fixes
|
||||
* [Fixes] Fix crash race by making certificateChain() thread safe
|
||||
|
@ -925,6 +1099,10 @@ version 1.2.0 (release 2013-01-24 ), csync 0.70.2 required
|
|||
* [Platform] cmake fixes.
|
||||
* [Platform] Improved, more detailed error reporting.
|
||||
|
||||
|
||||
1.1 Series ChangeLog
|
||||
====================
|
||||
|
||||
version 1.1.4 (release 2012-12-19 ), csync 0.60.4 required
|
||||
* No changes to mirall, only csync fixes.
|
||||
|
||||
|
@ -934,7 +1112,7 @@ version 1.1.3 (release 2012-11-30 ), csync 0.60.3 required
|
|||
version 1.1.2 (release 2012-11-26 ), csync 0.60.2 required
|
||||
* [Fixes] Allow to properly cancel the password dialog.
|
||||
* [Fixes] Share folder name correctly percent encoded with old Qt
|
||||
4.6 builds ie. Debian.
|
||||
* * * * 4.6 builds ie. Debian.
|
||||
* [Fixes] If local sync dir is not existing, create it.
|
||||
* [Fixes] lots of other minor fixes.
|
||||
* [GUI] Display error messages in status dialog.
|
||||
|
@ -942,30 +1120,30 @@ version 1.1.2 (release 2012-11-26 ), csync 0.60.2 required
|
|||
* [GUI] Show username for connection in statusdialog.
|
||||
* [GUI] Show intro wizard on new connection setup.
|
||||
* [APP] Use CredentialStore to better support various credential
|
||||
backends.
|
||||
* * * backends.
|
||||
* [APP] Handle missing local folder more robust: Create it if
|
||||
missing instead of ignoring.
|
||||
* * * missing instead of ignoring.
|
||||
* [APP] Simplify treewalk code.
|
||||
* [Platform] Fix Mac building
|
||||
|
||||
version 1.1.1 (release 2012-10-18), csync 0.60.1 required
|
||||
* [GUI] Allow changing folder name in single folder mode
|
||||
* [GUI] Windows: Add license to installer
|
||||
* [GUI] owncloud --logwindow will bring up the log window
|
||||
in an already running instance
|
||||
* [GUI]* Allow changing folder name in single folder mode
|
||||
* [GUI]* Windows: Add license to installer
|
||||
* [GUI]* owncloud --logwindow will bring up the log window
|
||||
* * * * in an already running instance
|
||||
* [Fixes] Make sure SSL errors are always handled
|
||||
* [Fixes] Allow special characters in folder alias
|
||||
* [Fixes] Proper workaround for Menu bug in Ubuntu
|
||||
* [Fixes] csync: Fix improper memory cleanup which could
|
||||
cause memory leaks and crashes
|
||||
* * * * cause memory leaks and crashes
|
||||
* [Fixes] csync: Fix memory leak
|
||||
* [Fixes] csync: Allow single quote (') in file names
|
||||
* [Fixes] csync: Remove stray temporary files
|
||||
|
||||
* [GUI] Reworked tray context menu.
|
||||
* [GUI] Users can now sync the server root folder.
|
||||
* [GUI]* Reworked tray context menu.
|
||||
* [GUI]* Users can now sync the server root folder.
|
||||
* [Fixes] Proxy support: now supports Proxy Auto-Configuration (PAC)
|
||||
on Windows, reliability fixes across all OSes.
|
||||
* * * * on Windows, reliability fixes across all OSes.
|
||||
* [Fixes] Url entry field in setup assistant handles http/https correctly.
|
||||
* [Fixes] Button enable state in status dialog.
|
||||
* [Fixes] Crash fixed on ending the client, tray icon related.
|
||||
|
@ -978,11 +1156,15 @@ version 1.1.1 (release 2012-10-18), csync 0.60.1 required
|
|||
* [Platform] Windows: ownCloud gets added to autorun by default.
|
||||
* [Platform] insert correct version info from cmake.
|
||||
* [Platform] csync conf file and database were moved to the users app data
|
||||
directory, away from the .csync dir.
|
||||
* Renamed exclude.lst to sync-exclude.lst and moved it to
|
||||
/etc/appName()/ for more clean packaging. From the user path,
|
||||
still exclude.lst is read if sync-exclude.lst is not existing.
|
||||
* Placed custom.ini with customization options to /etc/appName()
|
||||
* * * * * directory, away from the .csync dir.
|
||||
** * * Renamed exclude.lst to sync-exclude.lst and moved it to
|
||||
* * * * /etc/appName()/ for more clean packaging. From the user path,
|
||||
* * * * still exclude.lst is read if sync-exclude.lst is not existing.
|
||||
** * * Placed custom.ini with customization options to /etc/appName()
|
||||
|
||||
|
||||
1.0 Series ChangeLog
|
||||
====================
|
||||
|
||||
version 1.0.5 (release 2012-08-14), csync 0.50.8 required
|
||||
* [Fixes] Fixed setup dialog: Really use https if checkbox is activated.
|
||||
|
@ -1000,23 +1182,23 @@ version 1.0.4 (release 2012-08-10), csync 0.50.8 required
|
|||
* [GUI] Removed Log Window Button, log available through command line.
|
||||
* [GUI] Proxy configuration dialog added.
|
||||
* [GUI] Added Translations to languages Slovenian, Polish, Catalan,
|
||||
Portuguese (Brazil), German, Greek, Spanish, Czech, Italian, Slovak,
|
||||
French, Russian, Japanese, Swedish, Portuguese (Portugal)
|
||||
all with translation rate >90%.
|
||||
* * * Portuguese (Brazil), German, Greek, Spanish, Czech, Italian, Slovak,
|
||||
* * * French, Russian, Japanese, Swedish, Portuguese (Portugal)
|
||||
* * * all with translation rate >90%.
|
||||
* [Fixes] Loading of self signed certs into Networkmanager (#oc-843)
|
||||
* [Fixes] Win32: Handle SSL dll loading correctly.
|
||||
* [Fixes] Many other small fixes and improvements.
|
||||
|
||||
version 1.0.3 (release 2012-06-19), csync 0.50.7 required
|
||||
* [GUI] Added a log window which catches the logging if required and
|
||||
allows to save for information.
|
||||
* * * allows to save for information.
|
||||
* [CMI] Added options --help, --logfile and --logflush
|
||||
* [APP] Allow to specify sync frequency in the config file.
|
||||
* [Fixes] Do not use csync database files from a sync before.
|
||||
* [Fixes] In Connection wizard, write the final config onyl if
|
||||
the user really accepted. Also remove the former database.
|
||||
* * * * the user really accepted. Also remove the former database.
|
||||
* [Fixes] More user expected behaviour deletion of sync folder local
|
||||
and remote.
|
||||
* * * * and remote.
|
||||
* [Fixes] Allow special characters in the sync directory names
|
||||
* [Fixes] Win32: Fixed directory removal with special character dirs.
|
||||
* [Fixes] MacOS: Do not flood the system log any more
|
||||
|
@ -1035,7 +1217,7 @@ version 1.0.2 (release 2012-05-18), csync 0.50.6 required
|
|||
* [Fixes] Dialogs comes to front on click
|
||||
* [Fixes] Open local sync folder from tray and status for win32
|
||||
* [Fixes] Load exclude.lst correctly on MacOSX
|
||||
+ csync fixes.
|
||||
* + csync fixes.
|
||||
|
||||
version 1.0.1 (release 2012-04-18), csync 0.50.5 required
|
||||
* [Security] Support SSL Connections
|
||||
|
|
|
@ -3,10 +3,10 @@ set( APPLICATION_SHORTNAME "Nextcloud" )
|
|||
set( APPLICATION_EXECUTABLE "nextcloud" )
|
||||
set( APPLICATION_DOMAIN "nextcloud.com" )
|
||||
set( APPLICATION_VENDOR "Nextcloud GmbH" )
|
||||
set( APPLICATION_UPDATE_URL "https://updates.nextcloud.org/client/" CACHE string "URL for updater" )
|
||||
set( APPLICATION_HELP_URL "" CACHE string "URL for the help menu" )
|
||||
set( APPLICATION_UPDATE_URL "https://updates.nextcloud.org/client/" CACHE STRING "URL for updater" )
|
||||
set( APPLICATION_HELP_URL "" CACHE STRING "URL for the help menu" )
|
||||
set( APPLICATION_ICON_NAME "Nextcloud" )
|
||||
set( APPLICATION_SERVER_URL "" CACHE string "URL for the server to use. If entered the server can only connect to this instance" )
|
||||
set( APPLICATION_SERVER_URL "" CACHE STRING "URL for the server to use. If entered the server can only connect to this instance" )
|
||||
|
||||
set( LINUX_PACKAGE_SHORTNAME "nextcloud" )
|
||||
|
||||
|
@ -20,14 +20,14 @@ set( MAC_INSTALLER_BACKGROUND_FILE "${CMAKE_SOURCE_DIR}/admin/osx/installer-back
|
|||
# set( APPLICATION_LICENSE "${OEM_THEME_DIR}/license.txt )
|
||||
|
||||
option( WITH_CRASHREPORTER "Build crashreporter" OFF )
|
||||
#set( CRASHREPORTER_SUBMIT_URL "https://crash-reports.owncloud.com/submit" CACHE string "URL for crash reporter" )
|
||||
#set( CRASHREPORTER_SUBMIT_URL "https://crash-reports.owncloud.com/submit" CACHE STRING "URL for crash reporter" )
|
||||
#set( CRASHREPORTER_ICON ":/owncloud-icon.png" )
|
||||
|
||||
option( WITH_PROVIDERS "Build with providers list" ON )
|
||||
|
||||
|
||||
## Theming options
|
||||
set( APPLICATION_WIZARD_HEADER_BACKGROUND_COLOR "#0082c9" CACHE string "Hex color of the wizard header background")
|
||||
set( APPLICATION_WIZARD_HEADER_TITLE_COLOR "#ffffff" CACHE string "Hex color of the text in the wizard header")
|
||||
set( APPLICATION_WIZARD_HEADER_BACKGROUND_COLOR "#0082c9" CACHE STRING "Hex color of the wizard header background")
|
||||
set( APPLICATION_WIZARD_HEADER_TITLE_COLOR "#ffffff" CACHE STRING "Hex color of the text in the wizard header")
|
||||
option( APPLICATION_WIZARD_USE_CUSTOM_LOGO "Use the logo from ':/client/theme/colored/wizard_logo.png' else the default application icon is used" ON )
|
||||
|
||||
|
|
24
README.md
|
@ -24,33 +24,27 @@ $ cd build
|
|||
```
|
||||
##### Compile and install
|
||||
|
||||
For development reasons it is better to install the client on user space
|
||||
instead on the global system. For example you could use in the next
|
||||
instructions `path-to-install-folder/` as `~/.local/` in a linux system. If
|
||||
you want to install system wide you could use `/usr/local` or `/opt/nextcloud/`.
|
||||
:warning: For development reasons it is better to **install the client on user space** instead on the global system. Mixing up libs/dll's of different version can lead to undefined behavior and crashes:
|
||||
|
||||
##### Linux
|
||||
* You could use the **cmake flag** ```CMAKE_INSTALL_PREFIX``` as ```~/.local/``` in a **Linux** system. If you want to install system wide you could use ```/usr/local``` or ```/opt/nextcloud/```.
|
||||
|
||||
* On **Windows 10** [```$USERPROFILE```](https://docs.microsoft.com/en-us/windows/deployment/usmt/usmt-recognized-environment-variables#a-href-idbkmk-2avariables-that-are-recognized-only-in-the-user-context) refers to ```C:\Users\<USERNAME>```.
|
||||
|
||||
##### Linux & Mac OS
|
||||
|
||||
```
|
||||
$ cmake .. -DCMAKE_INSTALL_PREFIX=path-to-install-folder/ -DCMAKE_BUILD_TYPE=Debug -DNO_SHIBBOLETH=1 -DQTKEYCHAIN_LIBRARY=/path-to-qt5keychain-folder/lib64/libqt5keychain.so -DQTKEYCHAIN_INCLUDE_DIR=/path-to-qt5keychain-folder/include/qt5keychain/ -DOPENSSL_ROOT_DIR=/path-to-openssl-folder/ -DOPENSSL_INCLUDE_DIR=path-to-openssl-folder/include -DOPENSSL_LIBRARIES=path-to-openssl-folder/lib
|
||||
$ cmake .. -DCMAKE_INSTALL_PREFIX=~/nextcloud-desktop-client -DCMAKE_BUILD_TYPE=Debug -DNO_SHIBBOLETH=1
|
||||
$ make install
|
||||
```
|
||||
|
||||
##### Windows
|
||||
|
||||
```
|
||||
$ cmake -G "Visual Studio 15 2017 Win64" .. -DCMAKE_INSTALL_PREFIX=path-to-install-folder/ -DCMAKE_BUILD_TYPE=Debug -DNO_SHIBBOLETH=1 -DPng2Ico_EXECUTABLE=/path-to-install-png2ico/png2ico.exe -DQTKEYCHAIN_LIBRARY=/path-to-qt5keychain-folder/lib/qt5keychain.lib -DQTKEYCHAIN_INCLUDE_DIR=/path-to-qt5keychain-folder/include/qt5keychain/ -DOPENSSL_ROOT_DIR=/path-to-openssl-folder/ -DOPENSSL_INCLUDE_DIR=path-to-openssl-folder/include -DOPENSSL_LIBRARIES=path-to-openssl-folder/lib
|
||||
$ cmake -G "Visual Studio 15 2017 Win64" .. -DCMAKE_INSTALL_PREFIX=$USERPROFILE\nextcloud-desktop-client -DCMAKE_BUILD_TYPE=Debug -DNO_SHIBBOLETH=1
|
||||
$ cmake --build . --config Debug --target install
|
||||
```
|
||||
|
||||
##### Mac OS
|
||||
|
||||
```
|
||||
$ cmake .. -DCMAKE_INSTALL_PREFIX=path-to-install-folder/ -DCMAKE_BUILD_TYPE=Debug -DNO_SHIBBOLETH=1 -DQTKEYCHAIN_LIBRARY=/path-to-qt5keychain-folder/lib/libqt5keychain.dylib -DQTKEYCHAIN_INCLUDE_DIR=/path-to-qt5keychain-folder/include/qt5keychain/ -DOPENSSL_ROOT_DIR=/path-to-openssl-folder/ -DOPENSSL_INCLUDE_DIR=path-to-openssl-folder/include -DOPENSSL_LIBRARIES=path-to-openssl-folder/lib
|
||||
$ make install
|
||||
```
|
||||
|
||||
More detailed instructions can be found at the [Desktop Client Wiki](https://github.com/nextcloud/desktop/wiki).
|
||||
:information_source: More detailed instructions can be found at the [Desktop Client Wiki](https://github.com/nextcloud/desktop/wiki).
|
||||
|
||||
### :inbox_tray: Where to find binaries to download
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
set( MIRALL_VERSION_MAJOR 2 )
|
||||
set( MIRALL_VERSION_MINOR 5 )
|
||||
set( MIRALL_VERSION_PATCH 2 )
|
||||
set( MIRALL_VERSION_MINOR 7 )
|
||||
set( MIRALL_VERSION_PATCH 0 )
|
||||
set( MIRALL_VERSION_YEAR 2019 )
|
||||
set( MIRALL_SOVERSION 0 )
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@ set -xe
|
|||
mkdir /app
|
||||
mkdir /build
|
||||
|
||||
#Set Qt-5.11
|
||||
export QT_BASE_DIR=/opt/qt511
|
||||
#Set Qt-5.12
|
||||
export QT_BASE_DIR=/opt/qt512
|
||||
export QTDIR=$QT_BASE_DIR
|
||||
export PATH=$QT_BASE_DIR/bin:$PATH
|
||||
export LD_LIBRARY_PATH=$QT_BASE_DIR/lib/x86_64-linux-gnu:$QT_BASE_DIR/lib:$LD_LIBRARY_PATH
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
nextcloud-client (2.3.3-1.0~cosmic1) cosmic; urgency=medium
|
||||
nextcloud-client (2.3.3-1.0~eoan1) eoan; urgency=medium
|
||||
|
||||
* Debian build support for the forked client.
|
||||
|
||||
-- István Váradi <ivaradi@varadiistvan.hu> Mon, 6 Nov 2017 20:20:04 +0100
|
||||
|
||||
nextcloud-client (2.3.1-1.0~cosmic1) cosmic; urgency=medium
|
||||
nextcloud-client (2.3.1-1.0~eoan1) eoan; urgency=medium
|
||||
|
||||
* New upstream version
|
||||
|
||||
-- István Váradi <ivaradi@varadiistvan.hu> Thu, 23 Mar 2017 19:07:36 +0100
|
||||
|
||||
nextcloud-client (2.3.0-1.0~cosmic1) cosmic; urgency=medium
|
||||
nextcloud-client (2.3.0-1.0~eoan1) eoan; urgency=medium
|
||||
|
||||
* New upstream version
|
||||
|
||||
-- István Váradi <ivaradi@varadiistvan.hu> Tue, 21 Mar 2017 19:34:13 +0100
|
||||
|
||||
nextcloud-client (2.2.4-1.4~cosmic1) cosmic; urgency=medium
|
||||
nextcloud-client (2.2.4-1.4~eoan1) eoan; urgency=medium
|
||||
|
||||
* The locale-specific icon names are correct too
|
||||
|
89
admin/linux/debian/debian.eoan/control
Normal file
|
@ -0,0 +1,89 @@
|
|||
Source: nextcloud-client
|
||||
Section: contrib/devel
|
||||
Priority: optional
|
||||
Maintainer: István Váradi <ivaradi@varadiistvan.hu>
|
||||
Build-Depends: cmake,
|
||||
debhelper,
|
||||
cdbs,
|
||||
dh-python,
|
||||
extra-cmake-modules (>= 5.16),
|
||||
kdelibs5-dev,
|
||||
libkf5kio-dev,
|
||||
libcmocka-dev,
|
||||
libhttp-dav-perl,
|
||||
libinotify-dev [kfreebsd-any],
|
||||
libqt5svg5-dev,
|
||||
libqt5webkit5-dev,
|
||||
libsqlite3-dev,
|
||||
libssl-dev (>= 1.1.0),
|
||||
zlib1g-dev,
|
||||
optipng,
|
||||
pkg-kde-tools,
|
||||
python-sphinx | python3-sphinx,
|
||||
python3-all,
|
||||
qt5keychain-dev,
|
||||
qtwebengine5-dev,
|
||||
qtdeclarative5-dev,
|
||||
qttools5-dev,
|
||||
qttools5-dev-tools,
|
||||
xvfb
|
||||
Standards-Version: 3.9.8
|
||||
Homepage: https://github.com/nextcloud/client_theming
|
||||
#Vcs-Git: git://anonscm.debian.org/collab-maint/nextcloud-client.git
|
||||
#Vcs-Browser: https://anonscm.debian.org/cgit/collab-maint/nextcloud-client.git
|
||||
|
||||
Package: nextcloud-client
|
||||
Architecture: any
|
||||
Depends: libnextcloudsync0 (=${binary:Version}), ${shlibs:Depends}, ${misc:Depends}, nextcloud-client-l10n
|
||||
Description: Nextcloud desktop sync client
|
||||
Use the desktop client to keep your files synchronized
|
||||
between your Nextcloud server and your desktop. Select
|
||||
one or more directories on your local machine and always
|
||||
have access to your latest files wherever you are.
|
||||
|
||||
Package: libnextcloudsync0
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends}, ${misc:Depends}
|
||||
Description: Nextcloud sync library
|
||||
Used by the Nextcloud desktop client as the synchronization engine.
|
||||
|
||||
Package: libnextcloudsync-dev
|
||||
Architecture: any
|
||||
Section: contrib/libdevel
|
||||
Depends: libnextcloudsync0 (=${binary:Version}), ${misc:Depends}
|
||||
Description: Nextcloud sync library development files
|
||||
The headers and development library for the Nextcloud sync library.
|
||||
|
||||
Package: nextcloud-client-l10n
|
||||
Architecture: all
|
||||
Depends: ${misc:Depends}
|
||||
Description: Nextcloud client internatialization files
|
||||
The translation files.
|
||||
|
||||
Package: nextcloud-client-nautilus
|
||||
Architecture: all
|
||||
Depends: nextcloud-client (>=${binary:Version}), libnextcloudsync0, python-nautilus, nautilus, ${misc:Depends}
|
||||
Description: Nautilus plugin for Nextcloud
|
||||
This package contains a Nautilus plugin to display
|
||||
synchronization status icons for Nextcloud files.
|
||||
|
||||
Package: nextcloud-client-nemo
|
||||
Architecture: all
|
||||
Depends: nextcloud-client (>=${binary:Version}), libnextcloudsync0, python-nemo | nemo-python, nemo, ${misc:Depends}
|
||||
Description: Nemo plugin for Nextcloud
|
||||
This package contains a Nemo plugin to display
|
||||
synchronization status icons for Nextcloud files.
|
||||
|
||||
Package: nextcloud-client-caja
|
||||
Architecture: all
|
||||
Depends: nextcloud-client (>=${binary:Version}), libnextcloudsync0, python-caja, caja, ${misc:Depends}
|
||||
Description: Caja plugin for Nextcloud
|
||||
This package contains a Caja plugin to display
|
||||
synchronization status icons for Nextcloud files.
|
||||
|
||||
Package: nextcloud-client-dolphin
|
||||
Architecture: any
|
||||
Depends: dolphin (>= 4:15.12.1), libnextcloudsync0 (= ${binary:Version}), nextcloud-client, ${misc:Depends}, ${shlibs:Depends}
|
||||
Description: Dolphin plugin for Nextcloud
|
||||
This package contains a Dolphin plugin to display
|
||||
synchronization status icons for Nextcloud files.
|
|
@ -1,40 +1,12 @@
|
|||
--- nextcloud-client-2.4.0.orig/src/gui/wizard/owncloudoauthcredspage.cpp
|
||||
+++ nextcloud-client-2.4.0/src/gui/wizard/owncloudoauthcredspage.cpp
|
||||
@@ -53,10 +53,8 @@ OwncloudOAuthCredsPage::OwncloudOAuthCredsPage()
|
||||
_ui.openLinkButton->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
QObject::connect(_ui.openLinkButton, &QWidget::customContextMenuRequested, [this](const QPoint &pos) {
|
||||
auto menu = new QMenu(_ui.openLinkButton);
|
||||
- menu->addAction(tr("Copy link to clipboard"), this, [this] {
|
||||
- if (_asyncAuth)
|
||||
- QApplication::clipboard()->setText(_asyncAuth->authorisationLink().toString(QUrl::FullyEncoded));
|
||||
- });
|
||||
+ auto action = menu->addAction(tr("Copy link to clipboard"));
|
||||
+ connect(action, &QAction::triggered, this, &OwncloudOAuthCredsPage::copyLinkToClipboard);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
menu->popup(_ui.openLinkButton->mapToGlobal(pos));
|
||||
});
|
||||
@@ -131,4 +129,11 @@ bool OwncloudOAuthCredsPage::isComplete() const
|
||||
return false; /* We can never go forward manually */
|
||||
--- nextcloud-client-2.5.3.orig/src/3rdparty/kmessagewidget/kmessagewidget.cpp 2019-07-26 18:40:34.949349387 +0000
|
||||
+++ nextcloud-client-2.5.3/src/3rdparty/kmessagewidget/kmessagewidget.cpp 2019-07-26 18:41:39.866478051 +0000
|
||||
@@ -105,6 +105,9 @@
|
||||
q->setMessageType(KMessageWidget::Information);
|
||||
}
|
||||
|
||||
+void OwncloudOAuthCredsPage::copyLinkToClipboard()
|
||||
+{
|
||||
+ if (_asyncAuth)
|
||||
+ QApplication::clipboard()->setText(_asyncAuth->authorisationLink().toString(QUrl::FullyEncoded));
|
||||
+}
|
||||
+template <typename T>
|
||||
+constexpr typename std::add_const<T>::type &qAsConst(T &t) noexcept { return t; }
|
||||
+
|
||||
+
|
||||
} // namespace OCC
|
||||
--- nextcloud-client-2.4.0.orig/src/gui/wizard/owncloudoauthcredspage.h
|
||||
+++ nextcloud-client-2.4.0/src/gui/wizard/owncloudoauthcredspage.h
|
||||
@@ -57,6 +57,10 @@ public:
|
||||
QString _refreshToken;
|
||||
QScopedPointer<OAuth> _asyncAuth;
|
||||
Ui_OwncloudOAuthCredsPage _ui;
|
||||
+
|
||||
+protected slots:
|
||||
+ void copyLinkToClipboard();
|
||||
+
|
||||
};
|
||||
|
||||
} // namespace OCC
|
||||
void KMessageWidgetPrivate::createLayout()
|
||||
{
|
||||
delete content->layout();
|
||||
|
|
|
@ -51,7 +51,7 @@ if ! wget http://ppa.launchpad.net/${repo}/ubuntu/pool/main/n/nextcloud-client/n
|
|||
origsourceopt="-sa"
|
||||
fi
|
||||
|
||||
for distribution in xenial bionic cosmic disco stable; do
|
||||
for distribution in xenial bionic disco eoan stable; do
|
||||
rm -rf nextcloud-client_${basever}
|
||||
cp -a ${DRONE_WORKSPACE} nextcloud-client_${basever}
|
||||
|
||||
|
|
|
@ -70,7 +70,9 @@ def collectEntries(baseCommit, baseVersion, kind):
|
|||
lastVersionTag = None
|
||||
lastCMAKEVersion = None
|
||||
for line in output.splitlines():
|
||||
(commit, name, email, date, revdate, subject) = line.split("\t")
|
||||
words = line.split("\t")
|
||||
(commit, name, email, date, revdate) = words[0:5]
|
||||
subject = "\t".join(words[5:])
|
||||
revdate = datetime.datetime.utcfromtimestamp(long(revdate)).strftime("%Y%m%d.%H%M%S")
|
||||
|
||||
kind = "beta"
|
||||
|
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 254 KiB |
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 151 KiB |
12
client.qrc
|
@ -1,14 +1,5 @@
|
|||
<RCC>
|
||||
<qresource prefix="/client">
|
||||
<file>resources/dialog-close.png</file>
|
||||
<file>resources/dialog-ok.png</file>
|
||||
<file>resources/dialog-cancel.png</file>
|
||||
<file>resources/folder-sync.png</file>
|
||||
<file>resources/folder-sync@2x.png</file>
|
||||
<file>resources/task-ongoing.png</file>
|
||||
<file>resources/view-refresh.png</file>
|
||||
<file>resources/warning.png</file>
|
||||
<file>resources/warning@2x.png</file>
|
||||
<file>resources/settings.png</file>
|
||||
<file>resources/settings@2x.png</file>
|
||||
<file>resources/activity.png</file>
|
||||
|
@ -26,7 +17,6 @@
|
|||
<file>resources/bell.svg</file>
|
||||
<file>resources/link.svg</file>
|
||||
<file>resources/files.svg</file>
|
||||
<file>resources/folder-grey.png</file>
|
||||
<file>resources/state-error.svg</file>
|
||||
<file>resources/state-warning.svg</file>
|
||||
<file>resources/folder.svg</file>
|
||||
|
@ -39,6 +29,8 @@
|
|||
<file>resources/state-sync.svg</file>
|
||||
<file>../desktop/resources/next.svg</file>
|
||||
<file>../desktop/resources/previous.svg</file>
|
||||
<file>resources/add.png</file>
|
||||
<file>resources/state-info.svg</file>
|
||||
</qresource>
|
||||
<qresource prefix="/"/>
|
||||
</RCC>
|
||||
|
|
|
@ -47,7 +47,13 @@ macro (KDE4_ADD_APP_ICON appsources pattern)
|
|||
endif (fn MATCHES ".*128.*")
|
||||
if (fn MATCHES ".*256.*" )
|
||||
list (APPEND _icons ${it})
|
||||
endif (fn MATCHES ".*256.*")
|
||||
endif (fn MATCHES ".*256.*")
|
||||
if (fn MATCHES ".*512.*" )
|
||||
list (APPEND _icons ${it})
|
||||
endif (fn MATCHES ".*512.*")
|
||||
if (fn MATCHES ".*1024.*" )
|
||||
list (APPEND _icons ${it})
|
||||
endif (fn MATCHES ".*1024.*")
|
||||
endforeach (it)
|
||||
if (_icons)
|
||||
add_custom_command(OUTPUT ${_outfilename}.ico ${_outfilename}.rc
|
||||
|
@ -104,14 +110,14 @@ macro (KDE4_ADD_APP_ICON appsources pattern)
|
|||
foreach (it ${files})
|
||||
if (it MATCHES ".*sidebar-16.*")
|
||||
configure_file(${it} ${appsources}.iconset/sidebar_16x16.png COPYONLY)
|
||||
elseif (it MATCHES ".*sidebar-18.*")
|
||||
configure_file(${it} ${appsources}.iconset/sidebar_18x18.png COPYONLY)
|
||||
elseif (it MATCHES ".*sidebar-32.*")
|
||||
configure_file(${it} ${appsources}.iconset/sidebar_18x18.png COPYONLY)
|
||||
configure_file(${it} ${appsources}.iconset/sidebar_16x16@2x.png COPYONLY)
|
||||
configure_file(${it} ${appsources}.iconset/sidebar_32x32.png COPYONLY)
|
||||
elseif (it MATCHES ".*sidebar-36.*")
|
||||
configure_file(${it} ${appsources}.iconset/sidebar_18x18@2x.png COPYONLY)
|
||||
elseif (it MATCHES ".*sidebar-64.*")
|
||||
configure_file(${it} ${appsources}.iconset/sidebar_18x18@2x.png COPYONLY)
|
||||
elseif (it MATCHES ".*sidebar-128.*")
|
||||
configure_file(${it} ${appsources}.iconset/sidebar_32x32.png COPYONLY)
|
||||
elseif (it MATCHES ".*sidebar-256.*")
|
||||
configure_file(${it} ${appsources}.iconset/sidebar_32x32@2x.png COPYONLY)
|
||||
endif()
|
||||
endforeach (it)
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
#
|
||||
# ecm_add_app_icon(<sources_var>
|
||||
# ICONS <icon> [<icon> [...]]
|
||||
# [SIDEBAR_ICONS <icon> [<icon> [...]] # Since 5.4x
|
||||
# [OUTFILE_BASE <name>]) # Since 5.4x
|
||||
# [SIDEBAR_ICONS <icon> [<icon> [...]] # Since 5.49
|
||||
# [OUTFILE_BASENAME <name>]) # Since 5.49
|
||||
# )
|
||||
#
|
||||
# The given icons, whose names must match the pattern::
|
||||
|
@ -27,20 +27,21 @@
|
|||
#
|
||||
# ``SIDEBAR_ICONS`` can be used to add Mac OS X sidebar
|
||||
# icons to the generated iconset. They are used when a folder monitored by the
|
||||
# application is dragged into Finder's sidebar. Since 5.4x.
|
||||
# application is dragged into Finder's sidebar. Since 5.49.
|
||||
#
|
||||
# ``OUTFILE_BASE`` will be used as the basename for the icon file. If
|
||||
# you specify it, the icon file will be called ``<OUTFILE_BASE>.icns`` on Mac OS X
|
||||
# and ``<OUTFILE_BASE>.ico`` on Windows. If you don't specify it, it defaults
|
||||
# to ``<sources_var>.<ext>``. Since 5.4x.
|
||||
# ``OUTFILE_BASENAME`` will be used as the basename for the icon file. If
|
||||
# you specify it, the icon file will be called ``<OUTFILE_BASENAME>.icns`` on Mac OS X
|
||||
# and ``<OUTFILE_BASENAME>.ico`` on Windows. If you don't specify it, it defaults
|
||||
# to ``<sources_var>.<ext>``. Since 5.49.
|
||||
#
|
||||
#
|
||||
# Windows notes
|
||||
# * Icons are compiled into the executable using a resource file.
|
||||
# * Icons may not show up in Windows Explorer if the executable
|
||||
# target does not have the ``WIN32_EXECUTABLE`` property set.
|
||||
# * The tool png2ico is required. See :find-module:`FindPng2Ico`.
|
||||
# * Supported sizes: 16, 32, 48, 64, 128.
|
||||
# * One of the tools png2ico (See :find-module:`FindPng2Ico`) or
|
||||
# icotool (see :find-module:`FindIcoTool`) is required.
|
||||
# * Supported sizes: 16, 24, 32, 48, 64, 128, 256, 512 and 1024.
|
||||
#
|
||||
# Mac OS X notes
|
||||
# * The executable target must have the ``MACOSX_BUNDLE`` property set.
|
||||
|
@ -101,7 +102,7 @@ include(CMakeParseArguments)
|
|||
|
||||
function(ecm_add_app_icon appsources)
|
||||
set(options)
|
||||
set(oneValueArgs OUTFILE_BASE)
|
||||
set(oneValueArgs OUTFILE_BASENAME)
|
||||
set(multiValueArgs ICONS SIDEBAR_ICONS)
|
||||
cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||
|
||||
|
@ -138,9 +139,9 @@ function(ecm_add_app_icon appsources)
|
|||
endif()
|
||||
|
||||
|
||||
_ecm_add_app_icon_categorize_icons("${ARG_ICONS}" "icons" "16;32;48;64;128;256;512;1024")
|
||||
_ecm_add_app_icon_categorize_icons("${ARG_ICONS}" "icons" "16;24;32;48;64;128;256;512;1024")
|
||||
if(ARG_SIDEBAR_ICONS)
|
||||
_ecm_add_app_icon_categorize_icons("${ARG_SIDEBAR_ICONS}" "sidebar_icons" "16;18;32;36;64")
|
||||
_ecm_add_app_icon_categorize_icons("${ARG_SIDEBAR_ICONS}" "sidebar_icons" "16;32;64;128;256")
|
||||
endif()
|
||||
|
||||
set(mac_icons
|
||||
|
@ -151,31 +152,37 @@ function(ecm_add_app_icon appsources)
|
|||
${icons_at_128px}
|
||||
${icons_at_256px}
|
||||
${icons_at_512px}
|
||||
${icons_at_1024px}
|
||||
${icons_at_1024px})
|
||||
|
||||
set(mac_sidebar_icons
|
||||
# Sidebar Icons: https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/Finder.html#//apple_ref/doc/uid/TP40014214-CH15-SW15
|
||||
${sidebar_icons_at_16px}
|
||||
${sidebar_icons_at_18px}
|
||||
${sidebar_icons_at_32px}
|
||||
${sidebar_icons_at_36px}
|
||||
${sidebar_icons_at_64px})
|
||||
if (NOT icons_at_128px)
|
||||
message(AUTHOR_WARNING "No 128px icon provided; this will not work on Mac OS X")
|
||||
${sidebar_icons_at_64px}
|
||||
${sidebar_icons_at_128px}
|
||||
${sidebar_icons_at_256px})
|
||||
|
||||
if (NOT (mac_icons OR mac_sidebar_icons))
|
||||
message(AUTHOR_WARNING "No icons suitable for use on macOS provided")
|
||||
endif()
|
||||
|
||||
|
||||
set(windows_icons ${icons_at_16px}
|
||||
${icons_at_32px}
|
||||
${icons_at_48px}
|
||||
${icons_at_64px}
|
||||
${icons_at_128px}
|
||||
${icons_at_256px})
|
||||
if (NOT windows_icons)
|
||||
set(windows_icons ${icons_at_16px}
|
||||
${icons_at_24px}
|
||||
${icons_at_32px}
|
||||
${icons_at_48px}
|
||||
${icons_at_64px}
|
||||
${icons_at_128px}
|
||||
${icons_at_256px}
|
||||
${icons_at_512px}
|
||||
${icons_at_1024px})
|
||||
|
||||
if (NOT (windows_icons))
|
||||
message(AUTHOR_WARNING "No icons suitable for use on Windows provided")
|
||||
endif()
|
||||
|
||||
if (ARG_OUTFILE_BASE)
|
||||
set (_outfilebasename "${ARG_OUTFILE_BASE}")
|
||||
if (ARG_OUTFILE_BASENAME)
|
||||
set (_outfilebasename "${ARG_OUTFILE_BASENAME}")
|
||||
else()
|
||||
set (_outfilebasename "${appsources}")
|
||||
endif()
|
||||
|
@ -185,26 +192,15 @@ function(ecm_add_app_icon appsources)
|
|||
set(saved_CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}")
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_FIND_MODULE_DIR})
|
||||
find_package(Png2Ico)
|
||||
find_package(IcoTool)
|
||||
set(CMAKE_MODULE_PATH "${saved_CMAKE_MODULE_PATH}")
|
||||
|
||||
if (Png2Ico_FOUND)
|
||||
if (Png2Ico_HAS_RCFILE_ARGUMENT)
|
||||
add_custom_command(
|
||||
OUTPUT "${_outfilename}.rc" "${_outfilename}.ico"
|
||||
COMMAND Png2Ico::Png2Ico
|
||||
ARGS
|
||||
--rcfile "${_outfilename}.rc"
|
||||
"${_outfilename}.ico"
|
||||
${windows_icons}
|
||||
DEPENDS ${windows_icons}
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
)
|
||||
else()
|
||||
function(create_windows_icon_and_rc command args deps)
|
||||
add_custom_command(
|
||||
OUTPUT "${_outfilename}.ico"
|
||||
COMMAND Png2Ico::Png2Ico
|
||||
ARGS "${_outfilename}.ico" ${windows_icons}
|
||||
DEPENDS ${windows_icons}
|
||||
COMMAND ${command}
|
||||
ARGS ${args}
|
||||
DEPENDS ${deps}
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
)
|
||||
# this bit's a little hacky to make the dependency stuff work
|
||||
|
@ -216,12 +212,72 @@ function(ecm_add_app_icon appsources)
|
|||
DEPENDS "${_outfilename}.ico"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
|
||||
)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
if (IcoTool_FOUND)
|
||||
list(APPEND icotool_args "-c" "-o" "${_outfilename}.ico")
|
||||
|
||||
# According to https://stackoverflow.com/a/40851713/2886832
|
||||
# Windows always chooses the first icon above 255px, all other ones will be ignored
|
||||
set(maxSize 0)
|
||||
foreach(size 256 512 1024)
|
||||
if(icons_at_${size}px)
|
||||
set(maxSize "${size}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
foreach(size 16 24 32 48 64 128 ${maxSize})
|
||||
if(NOT icons_at_${size}px)
|
||||
continue()
|
||||
endif()
|
||||
|
||||
set(icotool_icon_arg "")
|
||||
if(size STREQUAL "${maxSize}")
|
||||
# maxSize icon needs to be included as raw png
|
||||
list(APPEND icotool_args "-r")
|
||||
endif()
|
||||
|
||||
foreach(icon ${icons_at_${size}px})
|
||||
list(APPEND icotool_args "${icons_at_${size}px}")
|
||||
endforeach()
|
||||
endforeach()
|
||||
|
||||
create_windows_icon_and_rc(IcoTool::IcoTool "${icotool_args}" "${windows_icons_modern}")
|
||||
set(${appsources} "${${appsources}};${_outfilename}.rc" PARENT_SCOPE)
|
||||
|
||||
# standard png2ico has no rcfile argument
|
||||
# NOTE: We generally use https://github.com/hiiamok/png2ImageMagickICO
|
||||
# or similar on windows, which is why we provide resolutions >= 256px here.
|
||||
# Standard png2ico will fail with this.
|
||||
elseif(Png2Ico_FOUND AND NOT Png2Ico_HAS_RCFILE_ARGUMENT AND windows_icons)
|
||||
set(png2ico_args)
|
||||
list(APPEND png2ico_args "${_outfilename}.ico")
|
||||
list(APPEND png2ico_args "${windows_icons}")
|
||||
create_windows_icon_and_rc(Png2Ico::Png2Ico "${png2ico_args}" "${windows_icons}")
|
||||
|
||||
set(${appsources} "${${appsources}};${_outfilename}.rc" PARENT_SCOPE)
|
||||
|
||||
# png2ico from kdewin provides rcfile argument
|
||||
elseif(Png2Ico_FOUND AND windows_icons)
|
||||
add_custom_command(
|
||||
OUTPUT "${_outfilename}.rc" "${_outfilename}.ico"
|
||||
COMMAND Png2Ico::Png2Ico
|
||||
ARGS
|
||||
--rcfile "${_outfilename}.rc"
|
||||
"${_outfilename}.ico"
|
||||
${windows_icons}
|
||||
DEPENDS ${windows_icons}
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
)
|
||||
|
||||
set(${appsources} "${${appsources}};${_outfilename}.rc" PARENT_SCOPE)
|
||||
|
||||
# else none of the supported tools was found
|
||||
else()
|
||||
message(WARNING "Unable to find the png2ico utility - application will not have an application icon!")
|
||||
message(WARNING "Unable to find the png2ico or icotool utilities or icons in matching sizes - application will not have an application icon!")
|
||||
endif()
|
||||
elseif (APPLE AND mac_icons)
|
||||
|
||||
elseif (APPLE AND (mac_icons OR mac_sidebar_icons))
|
||||
# first generate .iconset directory structure, then convert to .icns format using the Mac OS X "iconutil" utility,
|
||||
# to create retina compatible icon, you need png source files in pixel resolution 16x16, 32x32, 64x64, 128x128,
|
||||
# 256x256, 512x512, 1024x1024
|
||||
|
@ -246,8 +302,11 @@ function(ecm_add_app_icon appsources)
|
|||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
)
|
||||
list(APPEND iconset_icons
|
||||
"${_outfilename}.iconset/${type}_${sizename}.png")
|
||||
"${_outfilename}.iconset/${type}_${sizename}.png")
|
||||
endmacro()
|
||||
|
||||
# List of supported sizes and filenames taken from:
|
||||
# https://developer.apple.com/library/content/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Optimizing/Optimizing.html#//apple_ref/doc/uid/TP40012302-CH7-SW4
|
||||
foreach(size 16 32 128 256 512)
|
||||
math(EXPR double_size "2 * ${size}")
|
||||
foreach(file ${icons_at_${size}px})
|
||||
|
@ -258,14 +317,25 @@ function(ecm_add_app_icon appsources)
|
|||
endforeach()
|
||||
endforeach()
|
||||
|
||||
foreach(size 16 18 32)
|
||||
math(EXPR double_size "2 * ${size}")
|
||||
foreach(file ${sidebar_icons_at_${size}px})
|
||||
copy_icon("${file}" "${size}x${size}" "sidebar")
|
||||
endforeach()
|
||||
foreach(file ${sidebar_icons_at_${double_size}px})
|
||||
copy_icon("${file}" "${size}x${size}@2x" "sidebar")
|
||||
endforeach()
|
||||
# List of supported sizes and filenames taken from:
|
||||
# https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/Finder.html#//apple_ref/doc/uid/TP40014214-CH15-SW15
|
||||
foreach(file ${sidebar_icons_at_16px})
|
||||
copy_icon("${file}" "16x16" "sidebar")
|
||||
endforeach()
|
||||
foreach(file ${sidebar_icons_at_32px})
|
||||
copy_icon("${file}" "16x16@2x" "sidebar")
|
||||
endforeach()
|
||||
foreach(file ${sidebar_icons_at_32px})
|
||||
copy_icon("${file}" "18x18" "sidebar")
|
||||
endforeach()
|
||||
foreach(file ${sidebar_icons_at_64px})
|
||||
copy_icon("${file}" "18x18@2x" "sidebar")
|
||||
endforeach()
|
||||
foreach(file ${sidebar_icons_at_128px})
|
||||
copy_icon("${file}" "32x32" "sidebar")
|
||||
endforeach()
|
||||
foreach(file ${sidebar_icons_at_256px})
|
||||
copy_icon("${file}" "32x32@2x" "sidebar")
|
||||
endforeach()
|
||||
|
||||
# generate .icns icon file
|
||||
|
|
|
@ -99,7 +99,7 @@ Activity
|
|||
.. index:: activity, recent changes, sync activity
|
||||
|
||||
The Activity window, which can be invoked either from the main menu (``Recent
|
||||
Changes -> Details…``) or the Activity tab on the left side of the settings
|
||||
Changes -> View more activity…``) or the Activity tab on the left side of the settings
|
||||
window, provides an in-depth account of the recent sync activity. It will show
|
||||
files that have not been synced because they are on the ignored files list, or
|
||||
because they cannot be synced in a cross-platform manner due to containing
|
||||
|
|
BIN
resources/add.png
Normal file
After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 668 B |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 7.5 KiB |
1
resources/state-info.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 4.2333 4.2333" version="1.1" height="16" width="16"><g id="g3830" transform="matrix(.87498 0 0 .87498 .26458 -255.9)"><circle id="circle3818" stroke-width=".25066" fill="#2268ab" r="2.1167" cy="294.88" cx="2.1167" /><g id="g3828" stroke-linejoin="round" stroke-linecap="round" fill="none" /></g><path style="fill:#ffffff;stroke-width:0.17859235" d="m 1.6076619,2.1122981 c 0.027682,0.068222 0.058043,0.1232286 0.115014,0.043934 0.072686,-0.047862 0.314322,-0.2548509 0.29682,-0.061078 C 1.953774,2.4553739 1.8705497,2.8125586 1.8105428,3.1738508 1.7403561,3.3728027 1.9237704,3.5430012 2.1028984,3.4078068 2.295421,3.3181535 2.4582973,3.1779584 2.6256382,3.0488362 2.599921,2.9911507 2.5809903,2.9077482 2.5191973,2.9868644 2.4356161,3.0297263 2.2566665,3.2222491 2.2163047,3.07116 2.2725613,2.681829 2.3904322,2.3041062 2.4600833,1.9170966 2.5309844,1.7376113 2.3950755,1.5200858 2.210054,1.6736753 1.985742,1.7836882 1.8010774,1.9562083 1.6076619,2.1122981 Z M 2.4041839,0.77839186 C 2.1702279,0.77446305 2.0636081,1.1609366 2.2889917,1.2561264 2.4716917,1.3236342 2.659928,1.1286114 2.6086721,0.94358974 2.5911701,0.8467927 2.5018738,0.77035521 2.4038266,0.77749894 Z" /></svg>
|
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 596 B |
Before Width: | Height: | Size: 1.3 KiB |
|
@ -41,6 +41,7 @@ macro(libcloudproviders_add_config _sources)
|
|||
endmacro(libcloudproviders_add_config _sources)
|
||||
|
||||
|
||||
find_package(Qt5 5.6 COMPONENTS DBus)
|
||||
IF (UNIX AND Qt5DBus_FOUND AND LIBCLOUDPROVIDERS_FOUND)
|
||||
STRING(TOLOWER "${APPLICATION_VENDOR}" DBUS_VENDOR)
|
||||
STRING(REGEX REPLACE "[^A-z0-9]" "" DBUS_VENDOR "${DBUS_VENDOR}")
|
||||
|
|
512
src/3rdparty/kmessagewidget/kmessagewidget.cpp
vendored
Normal file
|
@ -0,0 +1,512 @@
|
|||
/* This file is part of the KDE libraries
|
||||
*
|
||||
* Copyright (c) 2011 Aurélien Gâteau <agateau@kde.org>
|
||||
* Copyright (c) 2014 Dominik Haumann <dhaumann@kde.org>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*/
|
||||
#include "kmessagewidget.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QApplication>
|
||||
#include <QEvent>
|
||||
#include <QGridLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QPainter>
|
||||
#include <QShowEvent>
|
||||
#include <QTimeLine>
|
||||
#include <QToolButton>
|
||||
#include <QStyle>
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// KMessageWidgetPrivate
|
||||
//---------------------------------------------------------------------
|
||||
class KMessageWidgetPrivate
|
||||
{
|
||||
public:
|
||||
void init(KMessageWidget *);
|
||||
|
||||
KMessageWidget *q;
|
||||
QFrame *content = nullptr;
|
||||
QLabel *iconLabel = nullptr;
|
||||
QLabel *textLabel = nullptr;
|
||||
QToolButton *closeButton = nullptr;
|
||||
QTimeLine *timeLine = nullptr;
|
||||
QIcon icon;
|
||||
bool ignoreShowEventDoingAnimatedShow = false;
|
||||
|
||||
KMessageWidget::MessageType messageType;
|
||||
bool wordWrap;
|
||||
QList<QToolButton *> buttons;
|
||||
QPixmap contentSnapShot;
|
||||
|
||||
void createLayout();
|
||||
void applyStyleSheet();
|
||||
void updateSnapShot();
|
||||
void updateLayout();
|
||||
void slotTimeLineChanged(qreal);
|
||||
void slotTimeLineFinished();
|
||||
|
||||
int bestContentHeight() const;
|
||||
};
|
||||
|
||||
void KMessageWidgetPrivate::init(KMessageWidget *q_ptr)
|
||||
{
|
||||
q = q_ptr;
|
||||
|
||||
q->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
|
||||
|
||||
// Note: when changing the value 500, also update KMessageWidgetTest
|
||||
timeLine = new QTimeLine(500, q);
|
||||
QObject::connect(timeLine, SIGNAL(valueChanged(qreal)), q, SLOT(slotTimeLineChanged(qreal)));
|
||||
QObject::connect(timeLine, SIGNAL(finished()), q, SLOT(slotTimeLineFinished()));
|
||||
|
||||
content = new QFrame(q);
|
||||
content->setObjectName(QStringLiteral("contentWidget"));
|
||||
content->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
|
||||
wordWrap = false;
|
||||
|
||||
iconLabel = new QLabel(content);
|
||||
iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
iconLabel->hide();
|
||||
|
||||
textLabel = new QLabel(content);
|
||||
textLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
textLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
|
||||
QObject::connect(textLabel, &QLabel::linkActivated, q, &KMessageWidget::linkActivated);
|
||||
QObject::connect(textLabel, &QLabel::linkHovered, q, &KMessageWidget::linkHovered);
|
||||
|
||||
QAction *closeAction = new QAction(q);
|
||||
closeAction->setText(KMessageWidget::tr("&Close"));
|
||||
closeAction->setToolTip(KMessageWidget::tr("Close message"));
|
||||
closeAction->setIcon(QIcon(":/client/resources/close.svg")); // ivan: NC customization
|
||||
|
||||
QObject::connect(closeAction, &QAction::triggered, q, &KMessageWidget::animatedHide);
|
||||
|
||||
closeButton = new QToolButton(content);
|
||||
closeButton->setAutoRaise(true);
|
||||
closeButton->setDefaultAction(closeAction);
|
||||
|
||||
q->setMessageType(KMessageWidget::Information);
|
||||
}
|
||||
|
||||
void KMessageWidgetPrivate::createLayout()
|
||||
{
|
||||
delete content->layout();
|
||||
|
||||
content->resize(q->size());
|
||||
|
||||
qDeleteAll(buttons);
|
||||
buttons.clear();
|
||||
|
||||
Q_FOREACH (QAction *action, q->actions()) {
|
||||
QToolButton *button = new QToolButton(content);
|
||||
button->setDefaultAction(action);
|
||||
button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
||||
buttons.append(button);
|
||||
}
|
||||
|
||||
// AutoRaise reduces visual clutter, but we don't want to turn it on if
|
||||
// there are other buttons, otherwise the close button will look different
|
||||
// from the others.
|
||||
closeButton->setAutoRaise(buttons.isEmpty());
|
||||
|
||||
if (wordWrap) {
|
||||
QGridLayout *layout = new QGridLayout(content);
|
||||
// Set alignment to make sure icon does not move down if text wraps
|
||||
layout->addWidget(iconLabel, 0, 0, 1, 1, Qt::AlignHCenter | Qt::AlignTop);
|
||||
layout->addWidget(textLabel, 0, 1);
|
||||
|
||||
if (buttons.isEmpty()) {
|
||||
// Use top-vertical alignment like the icon does.
|
||||
layout->addWidget(closeButton, 0, 2, 1, 1, Qt::AlignHCenter | Qt::AlignTop);
|
||||
} else {
|
||||
// Use an additional layout in row 1 for the buttons.
|
||||
QHBoxLayout *buttonLayout = new QHBoxLayout;
|
||||
buttonLayout->addStretch();
|
||||
Q_FOREACH (QToolButton *button, buttons) {
|
||||
// For some reason, calling show() is necessary if wordwrap is true,
|
||||
// otherwise the buttons do not show up. It is not needed if
|
||||
// wordwrap is false.
|
||||
button->show();
|
||||
buttonLayout->addWidget(button);
|
||||
}
|
||||
buttonLayout->addWidget(closeButton);
|
||||
layout->addItem(buttonLayout, 1, 0, 1, 2);
|
||||
}
|
||||
} else {
|
||||
QHBoxLayout *layout = new QHBoxLayout(content);
|
||||
layout->addWidget(iconLabel);
|
||||
layout->addWidget(textLabel);
|
||||
|
||||
for (QToolButton *button : qAsConst(buttons)) {
|
||||
layout->addWidget(button);
|
||||
}
|
||||
|
||||
layout->addWidget(closeButton);
|
||||
};
|
||||
|
||||
if (q->isVisible()) {
|
||||
q->setFixedHeight(content->sizeHint().height());
|
||||
}
|
||||
q->updateGeometry();
|
||||
}
|
||||
|
||||
void KMessageWidgetPrivate::applyStyleSheet()
|
||||
{
|
||||
QColor bgBaseColor;
|
||||
|
||||
// We have to hardcode colors here because KWidgetsAddons is a tier 1 framework
|
||||
// and therefore can't depend on any other KDE Frameworks
|
||||
// The following RGB color values come from the "default" scheme in kcolorscheme.cpp
|
||||
switch (messageType) {
|
||||
case KMessageWidget::Positive:
|
||||
bgBaseColor.setRgb(39, 174, 96); // Window: ForegroundPositive
|
||||
break;
|
||||
case KMessageWidget::Information:
|
||||
bgBaseColor.setRgb(61, 174, 233); // Window: ForegroundActive
|
||||
break;
|
||||
case KMessageWidget::Warning:
|
||||
bgBaseColor.setRgb(246, 116, 0); // Window: ForegroundNeutral
|
||||
break;
|
||||
case KMessageWidget::Error:
|
||||
bgBaseColor.setRgb(218, 68, 83); // Window: ForegroundNegative
|
||||
break;
|
||||
}
|
||||
const qreal bgBaseColorAlpha = 0.2;
|
||||
bgBaseColor.setAlphaF(bgBaseColorAlpha);
|
||||
|
||||
const QPalette palette = QGuiApplication::palette();
|
||||
const QColor windowColor = palette.window().color();
|
||||
const QColor textColor = palette.text().color();
|
||||
const QColor border = bgBaseColor;
|
||||
|
||||
// Generate a final background color from overlaying bgBaseColor over windowColor
|
||||
const int newRed = (bgBaseColor.red() * bgBaseColorAlpha) + (windowColor.red() * (1 - bgBaseColorAlpha));
|
||||
const int newGreen = (bgBaseColor.green() * bgBaseColorAlpha) + (windowColor.green() * (1 - bgBaseColorAlpha));
|
||||
const int newBlue = (bgBaseColor.blue() * bgBaseColorAlpha) + (windowColor.blue() * (1 - bgBaseColorAlpha));
|
||||
|
||||
const QColor bgFinalColor = QColor(newRed, newGreen, newBlue);
|
||||
|
||||
content->setStyleSheet(
|
||||
QString::fromLatin1(".QFrame {"
|
||||
"background-color: %1;"
|
||||
"border-radius: 4px;"
|
||||
"border: 2px solid %2;"
|
||||
"margin: %3px;"
|
||||
"}"
|
||||
".QLabel { color: %4; }"
|
||||
)
|
||||
.arg(bgFinalColor.name())
|
||||
.arg(border.name())
|
||||
// DefaultFrameWidth returns the size of the external margin + border width. We know our border is 1px, so we subtract this from the frame normal QStyle FrameWidth to get our margin
|
||||
.arg(q->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, q) - 1)
|
||||
.arg(textColor.name())
|
||||
);
|
||||
}
|
||||
|
||||
void KMessageWidgetPrivate::updateLayout()
|
||||
{
|
||||
if (content->layout()) {
|
||||
createLayout();
|
||||
}
|
||||
}
|
||||
|
||||
void KMessageWidgetPrivate::updateSnapShot()
|
||||
{
|
||||
// Attention: updateSnapShot calls QWidget::render(), which causes the whole
|
||||
// window layouts to be activated. Calling this method from resizeEvent()
|
||||
// can lead to infinite recursion, see:
|
||||
// https://bugs.kde.org/show_bug.cgi?id=311336
|
||||
contentSnapShot = QPixmap(content->size() * q->devicePixelRatio());
|
||||
contentSnapShot.setDevicePixelRatio(q->devicePixelRatio());
|
||||
contentSnapShot.fill(Qt::transparent);
|
||||
content->render(&contentSnapShot, QPoint(), QRegion(), QWidget::DrawChildren);
|
||||
}
|
||||
|
||||
void KMessageWidgetPrivate::slotTimeLineChanged(qreal value)
|
||||
{
|
||||
q->setFixedHeight(qMin(value * 2, qreal(1.0)) * content->height());
|
||||
q->update();
|
||||
}
|
||||
|
||||
void KMessageWidgetPrivate::slotTimeLineFinished()
|
||||
{
|
||||
if (timeLine->direction() == QTimeLine::Forward) {
|
||||
// Show
|
||||
// We set the whole geometry here, because it may be wrong if a
|
||||
// KMessageWidget is shown right when the toplevel window is created.
|
||||
content->setGeometry(0, 0, q->width(), bestContentHeight());
|
||||
|
||||
// notify about finished animation
|
||||
emit q->showAnimationFinished();
|
||||
} else {
|
||||
// hide and notify about finished animation
|
||||
q->hide();
|
||||
emit q->hideAnimationFinished();
|
||||
}
|
||||
}
|
||||
|
||||
int KMessageWidgetPrivate::bestContentHeight() const
|
||||
{
|
||||
int height = content->heightForWidth(q->width());
|
||||
if (height == -1) {
|
||||
height = content->sizeHint().height();
|
||||
}
|
||||
return height;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// KMessageWidget
|
||||
//---------------------------------------------------------------------
|
||||
KMessageWidget::KMessageWidget(QWidget *parent)
|
||||
: QFrame(parent)
|
||||
, d(new KMessageWidgetPrivate)
|
||||
{
|
||||
d->init(this);
|
||||
}
|
||||
|
||||
KMessageWidget::KMessageWidget(const QString &text, QWidget *parent)
|
||||
: QFrame(parent)
|
||||
, d(new KMessageWidgetPrivate)
|
||||
{
|
||||
d->init(this);
|
||||
setText(text);
|
||||
}
|
||||
|
||||
KMessageWidget::~KMessageWidget()
|
||||
{
|
||||
delete d;
|
||||
}
|
||||
|
||||
QString KMessageWidget::text() const
|
||||
{
|
||||
return d->textLabel->text();
|
||||
}
|
||||
|
||||
void KMessageWidget::setText(const QString &text)
|
||||
{
|
||||
d->textLabel->setText(text);
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
KMessageWidget::MessageType KMessageWidget::messageType() const
|
||||
{
|
||||
return d->messageType;
|
||||
}
|
||||
|
||||
void KMessageWidget::setMessageType(KMessageWidget::MessageType type)
|
||||
{
|
||||
d->messageType = type;
|
||||
d->applyStyleSheet();
|
||||
}
|
||||
|
||||
QSize KMessageWidget::sizeHint() const
|
||||
{
|
||||
ensurePolished();
|
||||
return d->content->sizeHint();
|
||||
}
|
||||
|
||||
QSize KMessageWidget::minimumSizeHint() const
|
||||
{
|
||||
ensurePolished();
|
||||
return d->content->minimumSizeHint();
|
||||
}
|
||||
|
||||
bool KMessageWidget::event(QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::Polish && !d->content->layout()) {
|
||||
d->createLayout();
|
||||
} else if (event->type() == QEvent::PaletteChange) {
|
||||
d->applyStyleSheet();
|
||||
} else if (event->type() == QEvent::Show && !d->ignoreShowEventDoingAnimatedShow) {
|
||||
if ((height() != d->content->height()) || (d->content->pos().y() != 0)) {
|
||||
d->content->move(0, 0);
|
||||
setFixedHeight(d->content->height());
|
||||
}
|
||||
}
|
||||
return QFrame::event(event);
|
||||
}
|
||||
|
||||
void KMessageWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QFrame::resizeEvent(event);
|
||||
|
||||
if (d->timeLine->state() == QTimeLine::NotRunning) {
|
||||
d->content->resize(width(), d->bestContentHeight());
|
||||
}
|
||||
}
|
||||
|
||||
int KMessageWidget::heightForWidth(int width) const
|
||||
{
|
||||
ensurePolished();
|
||||
return d->content->heightForWidth(width);
|
||||
}
|
||||
|
||||
void KMessageWidget::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
QFrame::paintEvent(event);
|
||||
if (d->timeLine->state() == QTimeLine::Running) {
|
||||
QPainter painter(this);
|
||||
painter.setOpacity(d->timeLine->currentValue() * d->timeLine->currentValue());
|
||||
painter.drawPixmap(0, 0, d->contentSnapShot);
|
||||
}
|
||||
}
|
||||
|
||||
bool KMessageWidget::wordWrap() const
|
||||
{
|
||||
return d->wordWrap;
|
||||
}
|
||||
|
||||
void KMessageWidget::setWordWrap(bool wordWrap)
|
||||
{
|
||||
d->wordWrap = wordWrap;
|
||||
d->textLabel->setWordWrap(wordWrap);
|
||||
QSizePolicy policy = sizePolicy();
|
||||
policy.setHeightForWidth(wordWrap);
|
||||
setSizePolicy(policy);
|
||||
d->updateLayout();
|
||||
// Without this, when user does wordWrap -> !wordWrap -> wordWrap, a minimum
|
||||
// height is set, causing the widget to be too high.
|
||||
// Mostly visible in test programs.
|
||||
if (wordWrap) {
|
||||
setMinimumHeight(0);
|
||||
}
|
||||
}
|
||||
|
||||
bool KMessageWidget::isCloseButtonVisible() const
|
||||
{
|
||||
return d->closeButton->isVisible();
|
||||
}
|
||||
|
||||
void KMessageWidget::setCloseButtonVisible(bool show)
|
||||
{
|
||||
d->closeButton->setVisible(show);
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void KMessageWidget::addAction(QAction *action)
|
||||
{
|
||||
QFrame::addAction(action);
|
||||
d->updateLayout();
|
||||
}
|
||||
|
||||
void KMessageWidget::removeAction(QAction *action)
|
||||
{
|
||||
QFrame::removeAction(action);
|
||||
d->updateLayout();
|
||||
}
|
||||
|
||||
void KMessageWidget::animatedShow()
|
||||
{
|
||||
// Test before styleHint, as there might have been a style change while animation was running
|
||||
if (isHideAnimationRunning()) {
|
||||
d->timeLine->stop();
|
||||
emit hideAnimationFinished();
|
||||
}
|
||||
|
||||
if (!style()->styleHint(QStyle::SH_Widget_Animate, nullptr, this)
|
||||
|| (parentWidget() && !parentWidget()->isVisible())) {
|
||||
show();
|
||||
emit showAnimationFinished();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isVisible() && (d->timeLine->state() == QTimeLine::NotRunning) && (height() == d->bestContentHeight()) && (d->content->pos().y() == 0)) {
|
||||
emit showAnimationFinished();
|
||||
return;
|
||||
}
|
||||
|
||||
d->ignoreShowEventDoingAnimatedShow = true;
|
||||
show();
|
||||
d->ignoreShowEventDoingAnimatedShow = false;
|
||||
setFixedHeight(0);
|
||||
int wantedHeight = d->bestContentHeight();
|
||||
d->content->setGeometry(0, -wantedHeight, width(), wantedHeight);
|
||||
|
||||
d->updateSnapShot();
|
||||
|
||||
d->timeLine->setDirection(QTimeLine::Forward);
|
||||
if (d->timeLine->state() == QTimeLine::NotRunning) {
|
||||
d->timeLine->start();
|
||||
}
|
||||
}
|
||||
|
||||
void KMessageWidget::animatedHide()
|
||||
{
|
||||
// test this before isVisible, as animatedShow might have been called directly before,
|
||||
// so the first timeline event is not yet done and the widget is still hidden
|
||||
// And before styleHint, as there might have been a style change while animation was running
|
||||
if (isShowAnimationRunning()) {
|
||||
d->timeLine->stop();
|
||||
emit showAnimationFinished();
|
||||
}
|
||||
|
||||
if (!style()->styleHint(QStyle::SH_Widget_Animate, nullptr, this)) {
|
||||
hide();
|
||||
emit hideAnimationFinished();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isVisible()) {
|
||||
// explicitly hide it, so it stays hidden in case it is only not visible due to the parents
|
||||
hide();
|
||||
emit hideAnimationFinished();
|
||||
return;
|
||||
}
|
||||
|
||||
d->content->move(0, -d->content->height());
|
||||
d->updateSnapShot();
|
||||
|
||||
d->timeLine->setDirection(QTimeLine::Backward);
|
||||
if (d->timeLine->state() == QTimeLine::NotRunning) {
|
||||
d->timeLine->start();
|
||||
}
|
||||
}
|
||||
|
||||
bool KMessageWidget::isHideAnimationRunning() const
|
||||
{
|
||||
return (d->timeLine->direction() == QTimeLine::Backward)
|
||||
&& (d->timeLine->state() == QTimeLine::Running);
|
||||
}
|
||||
|
||||
bool KMessageWidget::isShowAnimationRunning() const
|
||||
{
|
||||
return (d->timeLine->direction() == QTimeLine::Forward)
|
||||
&& (d->timeLine->state() == QTimeLine::Running);
|
||||
}
|
||||
|
||||
QIcon KMessageWidget::icon() const
|
||||
{
|
||||
return d->icon;
|
||||
}
|
||||
|
||||
void KMessageWidget::setIcon(const QIcon &icon)
|
||||
{
|
||||
d->icon = icon;
|
||||
if (d->icon.isNull()) {
|
||||
d->iconLabel->hide();
|
||||
} else {
|
||||
const int size = style()->pixelMetric(QStyle::PM_ToolBarIconSize);
|
||||
d->iconLabel->setPixmap(d->icon.pixmap(size));
|
||||
d->iconLabel->show();
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_kmessagewidget.cpp"
|
||||
|
346
src/3rdparty/kmessagewidget/kmessagewidget.h
vendored
Normal file
|
@ -0,0 +1,346 @@
|
|||
/* This file is part of the KDE libraries
|
||||
*
|
||||
* Copyright (c) 2011 Aurélien Gâteau <agateau@kde.org>
|
||||
* Copyright (c) 2014 Dominik Haumann <dhaumann@kde.org>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*/
|
||||
#ifndef KMESSAGEWIDGET_H
|
||||
#define KMESSAGEWIDGET_H
|
||||
|
||||
#include <QFrame>
|
||||
|
||||
class KMessageWidgetPrivate;
|
||||
|
||||
/**
|
||||
* @class KMessageWidget kmessagewidget.h KMessageWidget
|
||||
*
|
||||
* @short A widget to provide feedback or propose opportunistic interactions.
|
||||
*
|
||||
* KMessageWidget can be used to provide inline positive or negative
|
||||
* feedback, or to implement opportunistic interactions.
|
||||
*
|
||||
* As a feedback widget, KMessageWidget provides a less intrusive alternative
|
||||
* to "OK Only" message boxes. If you want to avoid a modal KMessageBox,
|
||||
* consider using KMessageWidget instead.
|
||||
*
|
||||
* Examples of KMessageWidget look as follows, all of them having an icon set
|
||||
* with setIcon(), and the first three show a close button:
|
||||
*
|
||||
* \image html kmessagewidget.png "KMessageWidget with different message types"
|
||||
*
|
||||
* <b>Negative feedback</b>
|
||||
*
|
||||
* The KMessageWidget can be used as a secondary indicator of failure: the
|
||||
* first indicator is usually the fact the action the user expected to happen
|
||||
* did not happen.
|
||||
*
|
||||
* Example: User fills a form, clicks "Submit".
|
||||
*
|
||||
* @li Expected feedback: form closes
|
||||
* @li First indicator of failure: form stays there
|
||||
* @li Second indicator of failure: a KMessageWidget appears on top of the
|
||||
* form, explaining the error condition
|
||||
*
|
||||
* When used to provide negative feedback, KMessageWidget should be placed
|
||||
* close to its context. In the case of a form, it should appear on top of the
|
||||
* form entries.
|
||||
*
|
||||
* KMessageWidget should get inserted in the existing layout. Space should not
|
||||
* be reserved for it, otherwise it becomes "dead space", ignored by the user.
|
||||
* KMessageWidget should also not appear as an overlay to prevent blocking
|
||||
* access to elements the user needs to interact with to fix the failure.
|
||||
*
|
||||
* <b>Positive feedback</b>
|
||||
*
|
||||
* KMessageWidget can be used for positive feedback but it shouldn't be
|
||||
* overused. It is often enough to provide feedback by simply showing the
|
||||
* results of an action.
|
||||
*
|
||||
* Examples of acceptable uses:
|
||||
*
|
||||
* @li Confirm success of "critical" transactions
|
||||
* @li Indicate completion of background tasks
|
||||
*
|
||||
* Example of unadapted uses:
|
||||
*
|
||||
* @li Indicate successful saving of a file
|
||||
* @li Indicate a file has been successfully removed
|
||||
*
|
||||
* <b>Opportunistic interaction</b>
|
||||
*
|
||||
* Opportunistic interaction is the situation where the application suggests to
|
||||
* the user an action he could be interested in perform, either based on an
|
||||
* action the user just triggered or an event which the application noticed.
|
||||
*
|
||||
* Example of acceptable uses:
|
||||
*
|
||||
* @li A browser can propose remembering a recently entered password
|
||||
* @li A music collection can propose ripping a CD which just got inserted
|
||||
* @li A chat application may notify the user a "special friend" just connected
|
||||
*
|
||||
* @author Aurélien Gâteau <agateau@kde.org>
|
||||
* @since 4.7
|
||||
*/
|
||||
class KMessageWidget : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString text READ text WRITE setText)
|
||||
Q_PROPERTY(bool wordWrap READ wordWrap WRITE setWordWrap)
|
||||
Q_PROPERTY(bool closeButtonVisible READ isCloseButtonVisible WRITE setCloseButtonVisible)
|
||||
Q_PROPERTY(MessageType messageType READ messageType WRITE setMessageType)
|
||||
Q_PROPERTY(QIcon icon READ icon WRITE setIcon)
|
||||
public:
|
||||
|
||||
/**
|
||||
* Available message types.
|
||||
* The background colors are chosen depending on the message type.
|
||||
*/
|
||||
enum MessageType {
|
||||
Positive,
|
||||
Information,
|
||||
Warning,
|
||||
Error
|
||||
};
|
||||
Q_ENUM(MessageType)
|
||||
|
||||
/**
|
||||
* Constructs a KMessageWidget with the specified @p parent.
|
||||
*/
|
||||
explicit KMessageWidget(QWidget *parent = nullptr);
|
||||
|
||||
/**
|
||||
* Constructs a KMessageWidget with the specified @p parent and
|
||||
* contents @p text.
|
||||
*/
|
||||
explicit KMessageWidget(const QString &text, QWidget *parent = nullptr);
|
||||
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
~KMessageWidget() override;
|
||||
|
||||
/**
|
||||
* Get the text of this message widget.
|
||||
* @see setText()
|
||||
*/
|
||||
QString text() const;
|
||||
|
||||
/**
|
||||
* Check whether word wrap is enabled.
|
||||
*
|
||||
* If word wrap is enabled, the message widget wraps the displayed text
|
||||
* as required to the available width of the widget. This is useful to
|
||||
* avoid breaking widget layouts.
|
||||
*
|
||||
* @see setWordWrap()
|
||||
*/
|
||||
bool wordWrap() const;
|
||||
|
||||
/**
|
||||
* Check whether the close button is visible.
|
||||
*
|
||||
* @see setCloseButtonVisible()
|
||||
*/
|
||||
bool isCloseButtonVisible() const;
|
||||
|
||||
/**
|
||||
* Get the type of this message.
|
||||
* By default, the type is set to KMessageWidget::Information.
|
||||
*
|
||||
* @see KMessageWidget::MessageType, setMessageType()
|
||||
*/
|
||||
MessageType messageType() const;
|
||||
|
||||
/**
|
||||
* Add @p action to the message widget.
|
||||
* For each action a button is added to the message widget in the
|
||||
* order the actions were added.
|
||||
*
|
||||
* @param action the action to add
|
||||
* @see removeAction(), QWidget::actions()
|
||||
*/
|
||||
void addAction(QAction *action);
|
||||
|
||||
/**
|
||||
* Remove @p action from the message widget.
|
||||
*
|
||||
* @param action the action to remove
|
||||
* @see KMessageWidget::MessageType, addAction(), setMessageType()
|
||||
*/
|
||||
void removeAction(QAction *action);
|
||||
|
||||
/**
|
||||
* Returns the preferred size of the message widget.
|
||||
*/
|
||||
QSize sizeHint() const override;
|
||||
|
||||
/**
|
||||
* Returns the minimum size of the message widget.
|
||||
*/
|
||||
QSize minimumSizeHint() const override;
|
||||
|
||||
/**
|
||||
* Returns the required height for @p width.
|
||||
* @param width the width in pixels
|
||||
*/
|
||||
int heightForWidth(int width) const override;
|
||||
|
||||
/**
|
||||
* The icon shown on the left of the text. By default, no icon is shown.
|
||||
* @since 4.11
|
||||
*/
|
||||
QIcon icon() const;
|
||||
|
||||
/**
|
||||
* Check whether the hide animation started by calling animatedHide()
|
||||
* is still running. If animations are disabled, this function always
|
||||
* returns @e false.
|
||||
*
|
||||
* @see animatedHide(), hideAnimationFinished()
|
||||
* @since 5.0
|
||||
*/
|
||||
bool isHideAnimationRunning() const;
|
||||
|
||||
/**
|
||||
* Check whether the show animation started by calling animatedShow()
|
||||
* is still running. If animations are disabled, this function always
|
||||
* returns @e false.
|
||||
*
|
||||
* @see animatedShow(), showAnimationFinished()
|
||||
* @since 5.0
|
||||
*/
|
||||
bool isShowAnimationRunning() const;
|
||||
|
||||
public Q_SLOTS:
|
||||
/**
|
||||
* Set the text of the message widget to @p text.
|
||||
* If the message widget is already visible, the text changes on the fly.
|
||||
*
|
||||
* @param text the text to display, rich text is allowed
|
||||
* @see text()
|
||||
*/
|
||||
void setText(const QString &text);
|
||||
|
||||
/**
|
||||
* Set word wrap to @p wordWrap. If word wrap is enabled, the text()
|
||||
* of the message widget is wrapped to fit the available width.
|
||||
* If word wrap is disabled, the message widget's minimum size is
|
||||
* such that the entire text fits.
|
||||
*
|
||||
* @param wordWrap disable/enable word wrap
|
||||
* @see wordWrap()
|
||||
*/
|
||||
void setWordWrap(bool wordWrap);
|
||||
|
||||
/**
|
||||
* Set the visibility of the close button. If @p visible is @e true,
|
||||
* a close button is shown that calls animatedHide() if clicked.
|
||||
*
|
||||
* @see closeButtonVisible(), animatedHide()
|
||||
*/
|
||||
void setCloseButtonVisible(bool visible);
|
||||
|
||||
/**
|
||||
* Set the message type to @p type.
|
||||
* By default, the message type is set to KMessageWidget::Information.
|
||||
* Appropriate colors are chosen to mimic the appearance of Kirigami's
|
||||
* InlineMessage.
|
||||
*
|
||||
* @see messageType(), KMessageWidget::MessageType
|
||||
*/
|
||||
void setMessageType(KMessageWidget::MessageType type);
|
||||
|
||||
/**
|
||||
* Show the widget using an animation.
|
||||
*/
|
||||
void animatedShow();
|
||||
|
||||
/**
|
||||
* Hide the widget using an animation.
|
||||
*/
|
||||
void animatedHide();
|
||||
|
||||
/**
|
||||
* Define an icon to be shown on the left of the text
|
||||
* @since 4.11
|
||||
*/
|
||||
void setIcon(const QIcon &icon);
|
||||
|
||||
Q_SIGNALS:
|
||||
/**
|
||||
* This signal is emitted when the user clicks a link in the text label.
|
||||
* The URL referred to by the href anchor is passed in contents.
|
||||
* @param contents text of the href anchor
|
||||
* @see QLabel::linkActivated()
|
||||
* @since 4.10
|
||||
*/
|
||||
void linkActivated(const QString &contents);
|
||||
|
||||
/**
|
||||
* This signal is emitted when the user hovers over a link in the text label.
|
||||
* The URL referred to by the href anchor is passed in contents.
|
||||
* @param contents text of the href anchor
|
||||
* @see QLabel::linkHovered()
|
||||
* @since 4.11
|
||||
*/
|
||||
void linkHovered(const QString &contents);
|
||||
|
||||
/**
|
||||
* This signal is emitted when the hide animation is finished, started by
|
||||
* calling animatedHide(). If animations are disabled, this signal is
|
||||
* emitted immediately after the message widget got hidden.
|
||||
*
|
||||
* @note This signal is @e not emitted if the widget was hidden by
|
||||
* calling hide(), so this signal is only useful in conjunction
|
||||
* with animatedHide().
|
||||
*
|
||||
* @see animatedHide()
|
||||
* @since 5.0
|
||||
*/
|
||||
void hideAnimationFinished();
|
||||
|
||||
/**
|
||||
* This signal is emitted when the show animation is finished, started by
|
||||
* calling animatedShow(). If animations are disabled, this signal is
|
||||
* emitted immediately after the message widget got shown.
|
||||
*
|
||||
* @note This signal is @e not emitted if the widget was shown by
|
||||
* calling show(), so this signal is only useful in conjunction
|
||||
* with animatedShow().
|
||||
*
|
||||
* @see animatedShow()
|
||||
* @since 5.0
|
||||
*/
|
||||
void showAnimationFinished();
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
bool event(QEvent *event) override;
|
||||
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
|
||||
private:
|
||||
KMessageWidgetPrivate *const d;
|
||||
friend class KMessageWidgetPrivate;
|
||||
|
||||
Q_PRIVATE_SLOT(d, void slotTimeLineChanged(qreal))
|
||||
Q_PRIVATE_SLOT(d, void slotTimeLineFinished())
|
||||
};
|
||||
|
||||
#endif /* KMESSAGEWIDGET_H */
|
|
@ -87,9 +87,36 @@ void setLaunchOnStartup_private(const QString &appName, const QString &guiName,
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: Right now only detection on toggle/startup, not when windows theme is switched while nextcloud is running
|
||||
static inline bool hasDarkSystray_private()
|
||||
{
|
||||
return true;
|
||||
bool hasDarkSystray = true;
|
||||
// Open registry key first, continue only on success (may be legitimately absent in earlier windows versions)
|
||||
HKEY hKey;
|
||||
LONG lRes = RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", 0, KEY_READ, &hKey);
|
||||
|
||||
// classical windows function - preserve buff size for DWORD, call ExW version, store regkey value in nResult
|
||||
if (lRes == ERROR_SUCCESS) {
|
||||
DWORD dwBufferSize(sizeof(DWORD));
|
||||
DWORD nResult(0);
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regqueryvalueexw
|
||||
LONG nError = ::RegQueryValueExW(hKey,
|
||||
L"SystemUsesLightTheme",
|
||||
NULL,
|
||||
NULL,
|
||||
reinterpret_cast<LPBYTE>(&nResult),
|
||||
&dwBufferSize);
|
||||
|
||||
// if RegQuery returned no error and light theme was found, change systray return value
|
||||
if (nError == ERROR_SUCCESS && nResult == 1)
|
||||
hasDarkSystray = false;
|
||||
|
||||
return hasDarkSystray;
|
||||
} else {
|
||||
// fallback to true if regkey could not be determined
|
||||
return hasDarkSystray;
|
||||
}
|
||||
}
|
||||
|
||||
QVariant Utility::registryGetKeyValue(HKEY hRootKey, const QString &subKey, const QString &valueName)
|
||||
|
|
|
@ -236,13 +236,29 @@ static CSYNC_EXCLUDE_TYPE _csync_excluded_common(const char *path, bool excludeC
|
|||
return match;
|
||||
}
|
||||
|
||||
static QByteArray leftIncludeLast(const QByteArray & arr, char c)
|
||||
{
|
||||
// left up to and including `c`
|
||||
return arr.left(arr.lastIndexOf(c, arr.size() - 2) + 1);
|
||||
}
|
||||
|
||||
using namespace OCC;
|
||||
|
||||
ExcludedFiles::ExcludedFiles()
|
||||
ExcludedFiles::ExcludedFiles(QString localPath)
|
||||
: _localPath(std::move(localPath))
|
||||
{
|
||||
Q_ASSERT(_localPath.endsWith("/"));
|
||||
// Windows used to use PathMatchSpec which allows *foo to match abc/deffoo.
|
||||
_wildcardsMatchSlash = Utility::isWindows();
|
||||
|
||||
// We're in a detached exclude probably coming from a partial sync or test
|
||||
if (_localPath.isEmpty())
|
||||
return;
|
||||
|
||||
// Load exclude file from base dir
|
||||
QFileInfo fi(_localPath + ".sync-exclude.lst");
|
||||
if (fi.isReadable())
|
||||
addInTreeExcludeFilePath(fi.absoluteFilePath());
|
||||
}
|
||||
|
||||
ExcludedFiles::~ExcludedFiles()
|
||||
|
@ -251,7 +267,13 @@ ExcludedFiles::~ExcludedFiles()
|
|||
|
||||
void ExcludedFiles::addExcludeFilePath(const QString &path)
|
||||
{
|
||||
_excludeFiles.insert(path);
|
||||
_excludeFiles[_localPath.toUtf8()].append(path);
|
||||
}
|
||||
|
||||
void ExcludedFiles::addInTreeExcludeFilePath(const QString &path)
|
||||
{
|
||||
BasePathByteArray basePath = leftIncludeLast(path.toUtf8(), '/');
|
||||
_excludeFiles[basePath].append(path);
|
||||
}
|
||||
|
||||
void ExcludedFiles::setExcludeConflictFiles(bool onoff)
|
||||
|
@ -261,9 +283,18 @@ void ExcludedFiles::setExcludeConflictFiles(bool onoff)
|
|||
|
||||
void ExcludedFiles::addManualExclude(const QByteArray &expr)
|
||||
{
|
||||
_manualExcludes.append(expr);
|
||||
_allExcludes.append(expr);
|
||||
prepare();
|
||||
addManualExclude(expr, _localPath.toUtf8());
|
||||
}
|
||||
|
||||
void ExcludedFiles::addManualExclude(const QByteArray &expr, const QByteArray &basePath)
|
||||
{
|
||||
Q_ASSERT(basePath.startsWith('/'));
|
||||
Q_ASSERT(basePath.endsWith('/'));
|
||||
|
||||
auto key = basePath;
|
||||
_manualExcludes[key].append(expr);
|
||||
_allExcludes[key].append(expr);
|
||||
prepare(key);
|
||||
}
|
||||
|
||||
void ExcludedFiles::clearManualExcludes()
|
||||
|
@ -278,26 +309,47 @@ void ExcludedFiles::setWildcardsMatchSlash(bool onoff)
|
|||
prepare();
|
||||
}
|
||||
|
||||
bool ExcludedFiles::loadExcludeFile(const QByteArray & basePath, const QString & file)
|
||||
{
|
||||
QFile f(file);
|
||||
if (!f.open(QIODevice::ReadOnly))
|
||||
return false;
|
||||
|
||||
while (!f.atEnd()) {
|
||||
QByteArray line = f.readLine().trimmed();
|
||||
if (line.isEmpty() || line.startsWith('#'))
|
||||
continue;
|
||||
csync_exclude_expand_escapes(line);
|
||||
_allExcludes[basePath].append(line);
|
||||
}
|
||||
prepare(basePath);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ExcludedFiles::reloadExcludeFiles()
|
||||
{
|
||||
_allExcludes.clear();
|
||||
// clear all regex
|
||||
_bnameTraversalRegexFile.clear();
|
||||
_bnameTraversalRegexDir.clear();
|
||||
_fullTraversalRegexFile.clear();
|
||||
_fullTraversalRegexDir.clear();
|
||||
_fullRegexFile.clear();
|
||||
_fullRegexDir.clear();
|
||||
|
||||
bool success = true;
|
||||
foreach (const QString &file, _excludeFiles) {
|
||||
QFile f(file);
|
||||
if (!f.open(QIODevice::ReadOnly)) {
|
||||
success = false;
|
||||
continue;
|
||||
}
|
||||
while (!f.atEnd()) {
|
||||
QByteArray line = f.readLine().trimmed();
|
||||
if (line.isEmpty() || line.startsWith('#'))
|
||||
continue;
|
||||
csync_exclude_expand_escapes(line);
|
||||
_allExcludes.append(line);
|
||||
for (auto basePath : _excludeFiles.keys()) {
|
||||
for (auto file : _excludeFiles.value(basePath)) {
|
||||
success = loadExcludeFile(basePath, file);
|
||||
}
|
||||
}
|
||||
_allExcludes.append(_manualExcludes);
|
||||
prepare();
|
||||
|
||||
auto endManual = _manualExcludes.cend();
|
||||
for (auto kv = _manualExcludes.cbegin(); kv != endManual; ++kv) {
|
||||
_allExcludes[kv.key()].append(kv.value());
|
||||
prepare(kv.key());
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
|
@ -311,13 +363,15 @@ bool ExcludedFiles::isExcluded(
|
|||
return true;
|
||||
}
|
||||
|
||||
//TODO this seems a waste, hidden files are ignored before hitting this function it seems
|
||||
if (excludeHidden) {
|
||||
QString path = filePath;
|
||||
// Check all path subcomponents, but to *not* check the base path:
|
||||
// We do want to be able to sync with a hidden folder as the target.
|
||||
while (path.size() > basePath.size()) {
|
||||
QFileInfo fi(path);
|
||||
if (fi.isHidden() || fi.fileName().startsWith(QLatin1Char('.'))) {
|
||||
if (fi.fileName() != ".sync-exclude.lst"
|
||||
&& (fi.isHidden() || fi.fileName().startsWith(QLatin1Char('.')))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -340,7 +394,7 @@ bool ExcludedFiles::isExcluded(
|
|||
return fullPatternMatch(relativePath.toUtf8(), type) != CSYNC_NOT_EXCLUDED;
|
||||
}
|
||||
|
||||
CSYNC_EXCLUDE_TYPE ExcludedFiles::traversalPatternMatch(const char *path, ItemType filetype) const
|
||||
CSYNC_EXCLUDE_TYPE ExcludedFiles::traversalPatternMatch(const char *path, ItemType filetype)
|
||||
{
|
||||
auto match = _csync_excluded_common(path, _excludeConflictFiles);
|
||||
if (match != CSYNC_NOT_EXCLUDED)
|
||||
|
@ -348,6 +402,15 @@ CSYNC_EXCLUDE_TYPE ExcludedFiles::traversalPatternMatch(const char *path, ItemTy
|
|||
if (_allExcludes.isEmpty())
|
||||
return CSYNC_NOT_EXCLUDED;
|
||||
|
||||
// Directories are guaranteed to be visited before their files
|
||||
if (filetype == ItemTypeDirectory) {
|
||||
QFileInfo fi = QFileInfo(_localPath + path + "/.sync-exclude.lst");
|
||||
if (fi.isReadable()) {
|
||||
addInTreeExcludeFilePath(fi.absoluteFilePath());
|
||||
loadExcludeFile(fi.absolutePath().toUtf8(), fi.absoluteFilePath());
|
||||
}
|
||||
}
|
||||
|
||||
// Check the bname part of the path to see whether the full
|
||||
// regex should be run.
|
||||
|
||||
|
@ -359,35 +422,53 @@ CSYNC_EXCLUDE_TYPE ExcludedFiles::traversalPatternMatch(const char *path, ItemTy
|
|||
}
|
||||
QString bnameStr = QString::fromUtf8(bname);
|
||||
|
||||
QRegularExpressionMatch m;
|
||||
if (filetype == ItemTypeDirectory) {
|
||||
m = _bnameTraversalRegexDir.match(bnameStr);
|
||||
} else {
|
||||
m = _bnameTraversalRegexFile.match(bnameStr);
|
||||
}
|
||||
if (!m.hasMatch())
|
||||
return CSYNC_NOT_EXCLUDED;
|
||||
if (m.capturedStart(QStringLiteral("exclude")) != -1) {
|
||||
return CSYNC_FILE_EXCLUDE_LIST;
|
||||
} else if (m.capturedStart(QStringLiteral("excluderemove")) != -1) {
|
||||
return CSYNC_FILE_EXCLUDE_AND_REMOVE;
|
||||
}
|
||||
QByteArray basePath(_localPath.toUtf8() + path);
|
||||
while (basePath.size() > _localPath.size()) {
|
||||
basePath = leftIncludeLast(basePath, '/');
|
||||
QRegularExpressionMatch m;
|
||||
if (filetype == ItemTypeDirectory
|
||||
&& _bnameTraversalRegexDir.contains(basePath)) {
|
||||
m = _bnameTraversalRegexDir[basePath].match(bnameStr);
|
||||
} else if (filetype == ItemTypeFile
|
||||
&& _bnameTraversalRegexFile.contains(basePath)) {
|
||||
m = _bnameTraversalRegexFile[basePath].match(bnameStr);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
// third capture: full path matching is triggered
|
||||
QString pathStr = QString::fromUtf8(path);
|
||||
|
||||
if (filetype == ItemTypeDirectory) {
|
||||
m = _fullTraversalRegexDir.match(pathStr);
|
||||
} else {
|
||||
m = _fullTraversalRegexFile.match(pathStr);
|
||||
}
|
||||
if (m.hasMatch()) {
|
||||
if (!m.hasMatch())
|
||||
return CSYNC_NOT_EXCLUDED;
|
||||
if (m.capturedStart(QStringLiteral("exclude")) != -1) {
|
||||
return CSYNC_FILE_EXCLUDE_LIST;
|
||||
} else if (m.capturedStart(QStringLiteral("excluderemove")) != -1) {
|
||||
return CSYNC_FILE_EXCLUDE_AND_REMOVE;
|
||||
}
|
||||
}
|
||||
|
||||
// third capture: full path matching is triggered
|
||||
QString pathStr = QString::fromUtf8(path);
|
||||
basePath = _localPath.toUtf8() + path;
|
||||
while (basePath.size() > _localPath.size()) {
|
||||
basePath = leftIncludeLast(basePath, '/');
|
||||
QRegularExpressionMatch m;
|
||||
if (filetype == ItemTypeDirectory
|
||||
&& _fullTraversalRegexDir.contains(basePath)) {
|
||||
m = _fullTraversalRegexDir[basePath].match(pathStr);
|
||||
} else if (filetype == ItemTypeFile
|
||||
&& _fullTraversalRegexFile.contains(basePath)) {
|
||||
m = _fullTraversalRegexFile[basePath].match(pathStr);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m.hasMatch()) {
|
||||
if (m.capturedStart(QStringLiteral("exclude")) != -1) {
|
||||
return CSYNC_FILE_EXCLUDE_LIST;
|
||||
} else if (m.capturedStart(QStringLiteral("excluderemove")) != -1) {
|
||||
return CSYNC_FILE_EXCLUDE_AND_REMOVE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return CSYNC_NOT_EXCLUDED;
|
||||
}
|
||||
|
||||
|
@ -400,23 +481,38 @@ CSYNC_EXCLUDE_TYPE ExcludedFiles::fullPatternMatch(const char *path, ItemType fi
|
|||
return CSYNC_NOT_EXCLUDED;
|
||||
|
||||
QString p = QString::fromUtf8(path);
|
||||
QRegularExpressionMatch m;
|
||||
if (filetype == ItemTypeDirectory) {
|
||||
m = _fullRegexDir.match(p);
|
||||
} else {
|
||||
m = _fullRegexFile.match(p);
|
||||
}
|
||||
if (m.hasMatch()) {
|
||||
if (m.capturedStart(QStringLiteral("exclude")) != -1) {
|
||||
return CSYNC_FILE_EXCLUDE_LIST;
|
||||
} else if (m.capturedStart(QStringLiteral("excluderemove")) != -1) {
|
||||
return CSYNC_FILE_EXCLUDE_AND_REMOVE;
|
||||
// `path` seems to always be relative to `_localPath`, the tests however have not been
|
||||
// written that way... this makes the tests happy for now. TODO Fix the tests at some point
|
||||
if (path[0] == '/')
|
||||
++path;
|
||||
|
||||
QByteArray basePath(_localPath.toUtf8() + path);
|
||||
while (basePath.size() > _localPath.size()) {
|
||||
basePath = leftIncludeLast(basePath, '/');
|
||||
QRegularExpressionMatch m;
|
||||
if (filetype == ItemTypeDirectory
|
||||
&& _fullRegexDir.contains(basePath)) {
|
||||
m = _fullRegexDir[basePath].match(p);
|
||||
} else if (filetype == ItemTypeFile
|
||||
&& _fullRegexFile.contains(basePath)) {
|
||||
m = _fullRegexFile[basePath].match(p);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m.hasMatch()) {
|
||||
if (m.capturedStart(QStringLiteral("exclude")) != -1) {
|
||||
return CSYNC_FILE_EXCLUDE_LIST;
|
||||
} else if (m.capturedStart(QStringLiteral("excluderemove")) != -1) {
|
||||
return CSYNC_FILE_EXCLUDE_AND_REMOVE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return CSYNC_NOT_EXCLUDED;
|
||||
}
|
||||
|
||||
auto ExcludedFiles::csyncTraversalMatchFun() const
|
||||
auto ExcludedFiles::csyncTraversalMatchFun()
|
||||
-> std::function<CSYNC_EXCLUDE_TYPE(const char *path, ItemType filetype)>
|
||||
{
|
||||
return [this](const char *path, ItemType filetype) { return this->traversalPatternMatch(path, filetype); };
|
||||
|
@ -555,6 +651,22 @@ static QString extractBnameTrigger(const QString &exclude, bool wildcardsMatchSl
|
|||
|
||||
void ExcludedFiles::prepare()
|
||||
{
|
||||
// clear all regex
|
||||
_bnameTraversalRegexFile.clear();
|
||||
_bnameTraversalRegexDir.clear();
|
||||
_fullTraversalRegexFile.clear();
|
||||
_fullTraversalRegexDir.clear();
|
||||
_fullRegexFile.clear();
|
||||
_fullRegexDir.clear();
|
||||
|
||||
for (auto const & basePath : _allExcludes.keys())
|
||||
prepare(basePath);
|
||||
}
|
||||
|
||||
void ExcludedFiles::prepare(const BasePathByteArray & basePath)
|
||||
{
|
||||
Q_ASSERT(_allExcludes.contains(basePath));
|
||||
|
||||
// Build regular expressions for the different cases.
|
||||
//
|
||||
// To compose the _bnameTraversalRegex, _fullTraversalRegex and _fullRegex
|
||||
|
@ -596,7 +708,7 @@ void ExcludedFiles::prepare()
|
|||
pattern.append(appendMe);
|
||||
};
|
||||
|
||||
for (auto exclude : _allExcludes) {
|
||||
for (auto exclude : _allExcludes.value(basePath)) {
|
||||
if (exclude[0] == '\n')
|
||||
continue; // empty line
|
||||
if (exclude[0] == '\r')
|
||||
|
@ -618,6 +730,15 @@ void ExcludedFiles::prepare()
|
|||
auto &fullFileDir = removeExcluded ? fullFileDirRemove : fullFileDirKeep;
|
||||
auto &fullDir = removeExcluded ? fullDirRemove : fullDirKeep;
|
||||
|
||||
if (fullPath) {
|
||||
// The full pattern is matched against a path relative to _localPath, however exclude is
|
||||
// relative to basePath at this point.
|
||||
// We know for sure that both _localPath and basePath are absolute and that basePath is
|
||||
// contained in _localPath. So we can simply remove it from the begining.
|
||||
auto relPath = basePath.mid(_localPath.size());
|
||||
// Make exclude relative to _localPath
|
||||
exclude.prepend(relPath);
|
||||
}
|
||||
auto regexExclude = convertToRegexpSyntax(QString::fromUtf8(exclude), _wildcardsMatchSlash);
|
||||
if (!fullPath) {
|
||||
regexAppend(bnameFileDir, bnameDir, regexExclude, matchDirOnly);
|
||||
|
@ -654,11 +775,11 @@ void ExcludedFiles::prepare()
|
|||
// (exclude)|(excluderemove)|(bname triggers).
|
||||
// If the third group matches, the fullActivatedRegex needs to be applied
|
||||
// to the full path.
|
||||
_bnameTraversalRegexFile.setPattern(
|
||||
_bnameTraversalRegexFile[basePath].setPattern(
|
||||
"^(?P<exclude>" + bnameFileDirKeep + ")$|"
|
||||
+ "^(?P<excluderemove>" + bnameFileDirRemove + ")$|"
|
||||
+ "^(?P<trigger>" + bnameTriggerFileDir + ")$");
|
||||
_bnameTraversalRegexDir.setPattern(
|
||||
_bnameTraversalRegexDir[basePath].setPattern(
|
||||
"^(?P<exclude>" + bnameFileDirKeep + "|" + bnameDirKeep + ")$|"
|
||||
+ "^(?P<excluderemove>" + bnameFileDirRemove + "|" + bnameDirRemove + ")$|"
|
||||
+ "^(?P<trigger>" + bnameTriggerFileDir + "|" + bnameTriggerDir + ")$");
|
||||
|
@ -667,13 +788,13 @@ void ExcludedFiles::prepare()
|
|||
// the bname regex matches. Its basic form is (exclude)|(excluderemove)".
|
||||
// This pattern can be much simpler than fullRegex since we can assume a traversal
|
||||
// situation and doesn't need to look for bname patterns in parent paths.
|
||||
_fullTraversalRegexFile.setPattern(
|
||||
_fullTraversalRegexFile[basePath].setPattern(
|
||||
QLatin1String("")
|
||||
// Full patterns are anchored to the beginning
|
||||
+ "^(?P<exclude>" + fullFileDirKeep + ")(?:$|/)"
|
||||
+ "|"
|
||||
+ "^(?P<excluderemove>" + fullFileDirRemove + ")(?:$|/)");
|
||||
_fullTraversalRegexDir.setPattern(
|
||||
_fullTraversalRegexDir[basePath].setPattern(
|
||||
QLatin1String("")
|
||||
+ "^(?P<exclude>" + fullFileDirKeep + "|" + fullDirKeep + ")(?:$|/)"
|
||||
+ "|"
|
||||
|
@ -681,7 +802,7 @@ void ExcludedFiles::prepare()
|
|||
|
||||
// The full regex is applied to the full path and incorporates both bname and
|
||||
// full-path patterns. It has the form "(exclude)|(excluderemove)".
|
||||
_fullRegexFile.setPattern(
|
||||
_fullRegexFile[basePath].setPattern(
|
||||
QLatin1String("(?P<exclude>")
|
||||
// Full patterns are anchored to the beginning
|
||||
+ "^(?:" + fullFileDirKeep + ")(?:$|/)" + "|"
|
||||
|
@ -697,7 +818,7 @@ void ExcludedFiles::prepare()
|
|||
+ "(?:^|/)(?:" + bnameFileDirRemove + ")(?:$|/)" + "|"
|
||||
+ "(?:^|/)(?:" + bnameDirRemove + ")/"
|
||||
+ ")");
|
||||
_fullRegexDir.setPattern(
|
||||
_fullRegexDir[basePath].setPattern(
|
||||
QLatin1String("(?P<exclude>")
|
||||
+ "^(?:" + fullFileDirKeep + "|" + fullDirKeep + ")(?:$|/)" + "|"
|
||||
+ "(?:^|/)(?:" + bnameFileDirKeep + "|" + bnameDirKeep + ")(?:$|/)"
|
||||
|
@ -711,16 +832,16 @@ void ExcludedFiles::prepare()
|
|||
QRegularExpression::PatternOptions patternOptions = QRegularExpression::NoPatternOption;
|
||||
if (OCC::Utility::fsCasePreserving())
|
||||
patternOptions |= QRegularExpression::CaseInsensitiveOption;
|
||||
_bnameTraversalRegexFile.setPatternOptions(patternOptions);
|
||||
_bnameTraversalRegexFile.optimize();
|
||||
_bnameTraversalRegexDir.setPatternOptions(patternOptions);
|
||||
_bnameTraversalRegexDir.optimize();
|
||||
_fullTraversalRegexFile.setPatternOptions(patternOptions);
|
||||
_fullTraversalRegexFile.optimize();
|
||||
_fullTraversalRegexDir.setPatternOptions(patternOptions);
|
||||
_fullTraversalRegexDir.optimize();
|
||||
_fullRegexFile.setPatternOptions(patternOptions);
|
||||
_fullRegexFile.optimize();
|
||||
_fullRegexDir.setPatternOptions(patternOptions);
|
||||
_fullRegexDir.optimize();
|
||||
_bnameTraversalRegexFile[basePath].setPatternOptions(patternOptions);
|
||||
_bnameTraversalRegexFile[basePath].optimize();
|
||||
_bnameTraversalRegexDir[basePath].setPatternOptions(patternOptions);
|
||||
_bnameTraversalRegexDir[basePath].optimize();
|
||||
_fullTraversalRegexFile[basePath].setPatternOptions(patternOptions);
|
||||
_fullTraversalRegexFile[basePath].optimize();
|
||||
_fullTraversalRegexDir[basePath].setPatternOptions(patternOptions);
|
||||
_fullTraversalRegexDir[basePath].optimize();
|
||||
_fullRegexFile[basePath].setPatternOptions(patternOptions);
|
||||
_fullRegexFile[basePath].optimize();
|
||||
_fullRegexDir[basePath].setPatternOptions(patternOptions);
|
||||
_fullRegexDir[basePath].optimize();
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ class OCSYNC_EXPORT ExcludedFiles : public QObject
|
|||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ExcludedFiles();
|
||||
ExcludedFiles(QString localPath = "/");
|
||||
~ExcludedFiles();
|
||||
|
||||
/**
|
||||
|
@ -75,6 +75,7 @@ public:
|
|||
* Does not load the file. Use reloadExcludeFiles() afterwards.
|
||||
*/
|
||||
void addExcludeFilePath(const QString &path);
|
||||
void addInTreeExcludeFilePath(const QString &path);
|
||||
|
||||
/**
|
||||
* Whether conflict files shall be excluded.
|
||||
|
@ -95,12 +96,13 @@ public:
|
|||
bool excludeHidden) const;
|
||||
|
||||
/**
|
||||
* Adds an exclude pattern.
|
||||
* Adds an exclude pattern anchored to base path
|
||||
*
|
||||
* Primarily used in tests. Patterns added this way are preserved when
|
||||
* reloadExcludeFiles() is called.
|
||||
*/
|
||||
void addManualExclude(const QByteArray &expr);
|
||||
void addManualExclude(const QByteArray &expr, const QByteArray &basePath);
|
||||
|
||||
/**
|
||||
* Removes all manually added exclude patterns.
|
||||
|
@ -121,7 +123,7 @@ public:
|
|||
* Careful: The function will only be valid for as long as this
|
||||
* ExcludedFiles instance stays alive.
|
||||
*/
|
||||
auto csyncTraversalMatchFun() const
|
||||
auto csyncTraversalMatchFun()
|
||||
-> std::function<CSYNC_EXCLUDE_TYPE(const char *path, ItemType filetype)>;
|
||||
|
||||
public slots:
|
||||
|
@ -129,6 +131,10 @@ public slots:
|
|||
* Reloads the exclude patterns from the registered paths.
|
||||
*/
|
||||
bool reloadExcludeFiles();
|
||||
/**
|
||||
* Loads the exclude patterns from file the registered base paths.
|
||||
*/
|
||||
bool loadExcludeFile(const QByteArray & basePath, const QString & file);
|
||||
|
||||
private:
|
||||
/**
|
||||
|
@ -156,10 +162,32 @@ private:
|
|||
* Note that this only matches patterns. It does not check whether the file
|
||||
* or directory pointed to is hidden (or whether it even exists).
|
||||
*/
|
||||
CSYNC_EXCLUDE_TYPE traversalPatternMatch(const char *path, ItemType filetype) const;
|
||||
CSYNC_EXCLUDE_TYPE traversalPatternMatch(const char *path, ItemType filetype);
|
||||
|
||||
// Our BasePath need to end with '/'
|
||||
class BasePathByteArray : public QByteArray
|
||||
{
|
||||
public:
|
||||
BasePathByteArray(QByteArray && other)
|
||||
: QByteArray(std::move(other))
|
||||
{
|
||||
Q_ASSERT(this->endsWith('/'));
|
||||
}
|
||||
|
||||
BasePathByteArray(const QByteArray & other)
|
||||
: QByteArray(other)
|
||||
{
|
||||
Q_ASSERT(this->endsWith('/'));
|
||||
}
|
||||
|
||||
BasePathByteArray(const char * data, int size = -1)
|
||||
: BasePathByteArray(QByteArray(data, size))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate optimized regular expressions for the exclude patterns.
|
||||
* Generate optimized regular expressions for the exclude patterns anchored to basePath.
|
||||
*
|
||||
* The optimization works in two steps: First, all supported patterns are put
|
||||
* into _fullRegexFile/_fullRegexDir. These regexes can be applied to the full
|
||||
|
@ -187,24 +215,28 @@ private:
|
|||
* full matcher would exclude. Example: "b" is excluded. traversal("b/c")
|
||||
* returns not-excluded because "c" isn't a bname activation pattern.
|
||||
*/
|
||||
void prepare(const BasePathByteArray & basePath);
|
||||
|
||||
void prepare();
|
||||
|
||||
|
||||
QString _localPath;
|
||||
/// Files to load excludes from
|
||||
QSet<QString> _excludeFiles;
|
||||
QMap<BasePathByteArray, QList<QString>> _excludeFiles;
|
||||
|
||||
/// Exclude patterns added with addManualExclude()
|
||||
QList<QByteArray> _manualExcludes;
|
||||
QMap<BasePathByteArray, QList<QByteArray>> _manualExcludes;
|
||||
|
||||
/// List of all active exclude patterns
|
||||
QList<QByteArray> _allExcludes;
|
||||
QMap<BasePathByteArray, QList<QByteArray>> _allExcludes;
|
||||
|
||||
/// see prepare()
|
||||
QRegularExpression _bnameTraversalRegexFile;
|
||||
QRegularExpression _bnameTraversalRegexDir;
|
||||
QRegularExpression _fullTraversalRegexFile;
|
||||
QRegularExpression _fullTraversalRegexDir;
|
||||
QRegularExpression _fullRegexFile;
|
||||
QRegularExpression _fullRegexDir;
|
||||
QMap<BasePathByteArray, QRegularExpression> _bnameTraversalRegexFile;
|
||||
QMap<BasePathByteArray, QRegularExpression> _bnameTraversalRegexDir;
|
||||
QMap<BasePathByteArray, QRegularExpression> _fullTraversalRegexFile;
|
||||
QMap<BasePathByteArray, QRegularExpression> _fullTraversalRegexDir;
|
||||
QMap<BasePathByteArray, QRegularExpression> _fullRegexFile;
|
||||
QMap<BasePathByteArray, QRegularExpression> _fullRegexDir;
|
||||
|
||||
bool _excludeConflictFiles = true;
|
||||
|
||||
|
|
|
@ -124,7 +124,9 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
|
|||
* because it's a hidden file that should not be synced.
|
||||
* This code should probably be in csync_exclude, but it does not have the fs parameter.
|
||||
* Keep it here for now */
|
||||
if (ctx->ignore_hidden_files && (fs->is_hidden)) {
|
||||
if (ctx->ignore_hidden_files
|
||||
&& fs->is_hidden
|
||||
&& !fs->path.endsWith(".sync-exclude.lst")) {
|
||||
qCInfo(lcUpdate, "file excluded because it is a hidden file: %s", fs->path.constData());
|
||||
excluded = CSYNC_FILE_EXCLUDE_HIDDEN;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ set(client_UI_SRCS
|
|||
generalsettings.ui
|
||||
legalnotice.ui
|
||||
ignorelisteditor.ui
|
||||
ignorelisttablewidget.ui
|
||||
networksettings.ui
|
||||
activitywidget.ui
|
||||
synclogdialog.ui
|
||||
|
@ -38,6 +39,8 @@ set(client_UI_SRCS
|
|||
wizard/owncloudconnectionmethoddialog.ui
|
||||
wizard/owncloudhttpcredspage.ui
|
||||
wizard/owncloudoauthcredspage.ui
|
||||
wizard/flow2authcredspage.ui
|
||||
wizard/flow2authwidget.ui
|
||||
wizard/owncloudsetupnocredspage.ui
|
||||
wizard/owncloudwizardresultpage.ui
|
||||
wizard/webview.ui
|
||||
|
@ -59,6 +62,7 @@ set(client_SRCS
|
|||
generalsettings.cpp
|
||||
legalnotice.cpp
|
||||
ignorelisteditor.cpp
|
||||
ignorelisttablewidget.cpp
|
||||
lockwatcher.cpp
|
||||
logbrowser.cpp
|
||||
navigationpanehelper.cpp
|
||||
|
@ -103,6 +107,7 @@ set(client_SRCS
|
|||
creds/credentialsfactory.cpp
|
||||
creds/httpcredentialsgui.cpp
|
||||
creds/oauth.cpp
|
||||
creds/flow2auth.cpp
|
||||
creds/webflowcredentials.cpp
|
||||
creds/webflowcredentialsdialog.cpp
|
||||
wizard/postfixlineedit.cpp
|
||||
|
@ -111,6 +116,8 @@ set(client_SRCS
|
|||
wizard/owncloudconnectionmethoddialog.cpp
|
||||
wizard/owncloudhttpcredspage.cpp
|
||||
wizard/owncloudoauthcredspage.cpp
|
||||
wizard/flow2authcredspage.cpp
|
||||
wizard/flow2authwidget.cpp
|
||||
wizard/owncloudsetuppage.cpp
|
||||
wizard/owncloudwizardcommon.cpp
|
||||
wizard/owncloudwizard.cpp
|
||||
|
@ -165,6 +172,7 @@ set(3rdparty_SRC
|
|||
../3rdparty/qtsingleapplication/qtlocalpeer.cpp
|
||||
../3rdparty/qtsingleapplication/qtsingleapplication.cpp
|
||||
../3rdparty/qtsingleapplication/qtsinglecoreapplication.cpp
|
||||
../3rdparty/kmessagewidget/kmessagewidget.cpp
|
||||
)
|
||||
|
||||
if (APPLE)
|
||||
|
@ -243,7 +251,7 @@ if(APPLE)
|
|||
file(GLOB_RECURSE OWNCLOUD_SIDEBAR_ICONS "${theme_dir}/colored/*-${APPLICATION_ICON_NAME}-sidebar*")
|
||||
MESSAGE(STATUS "OWNCLOUD_SIDEBAR_ICONS: ${APPLICATION_ICON_NAME}: ${OWNCLOUD_SIDEBAR_ICONS}")
|
||||
endif()
|
||||
ecm_add_app_icon(final_src ICONS "${OWNCLOUD_ICONS}" SIDEBAR_ICONS "${OWNCLOUD_SIDEBAR_ICONS}" OUTFILE_BASE "${APPLICATION_ICON_NAME}")
|
||||
ecm_add_app_icon(final_src ICONS "${OWNCLOUD_ICONS}" SIDEBAR_ICONS "${OWNCLOUD_SIDEBAR_ICONS}" OUTFILE_BASENAME "${APPLICATION_ICON_NAME}")
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIE")
|
||||
|
@ -265,6 +273,9 @@ if(NOT BUILD_OWNCLOUD_OSX_BUNDLE)
|
|||
endforeach(_file)
|
||||
install(FILES ${client_I18N} DESTINATION ${SHAREDIR}/${APPLICATION_EXECUTABLE}/i18n)
|
||||
else()
|
||||
file(GLOB_RECURSE VISUAL_ELEMENTS "${theme_dir}/colored/*-${APPLICATION_ICON_NAME}-w10startmenu*")
|
||||
install(FILES ${VISUAL_ELEMENTS} DESTINATION bin/visualelements)
|
||||
install(FILES "${theme_dir}/${APPLICATION_EXECUTABLE}.VisualElementsManifest.xml" DESTINATION bin)
|
||||
install(FILES ${client_I18N} DESTINATION i18n)
|
||||
endif()
|
||||
|
||||
|
@ -329,6 +340,7 @@ target_include_directories(${APPLICATION_EXECUTABLE} PRIVATE
|
|||
${CMAKE_SOURCE_DIR}/src/3rdparty/qtlockedfile
|
||||
${CMAKE_SOURCE_DIR}/src/3rdparty/qtmacgoodies/src
|
||||
${CMAKE_SOURCE_DIR}/src/3rdparty/qtsingleapplication
|
||||
${CMAKE_SOURCE_DIR}/src/3rdparty/kmessagewidget
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
|
||||
|
|
|
@ -342,9 +342,6 @@ AccountPtr AccountManager::createAccount()
|
|||
connect(acc.data(), &Account::proxyAuthenticationRequired,
|
||||
ProxyAuthHandler::instance(), &ProxyAuthHandler::handleProxyAuthenticationRequired);
|
||||
|
||||
connect(acc.data()->e2e(), &ClientSideEncryption::mnemonicGenerated,
|
||||
&AccountManager::displayMnemonic);
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
|
@ -364,6 +361,7 @@ void AccountManager::displayMnemonic(const QString& mnemonic)
|
|||
widget->exec();
|
||||
widget->resize(widget->sizeHint());
|
||||
}
|
||||
|
||||
void AccountManager::shutdown()
|
||||
{
|
||||
auto accountsCopy = _accounts;
|
||||
|
|
|
@ -35,10 +35,12 @@
|
|||
#include "filesystem.h"
|
||||
#include "clientsideencryptionjobs.h"
|
||||
#include "syncresult.h"
|
||||
#include "ignorelisttablewidget.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDir>
|
||||
#include <QListWidgetItem>
|
||||
#include <QMessageBox>
|
||||
|
@ -194,6 +196,14 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
|
|||
// Connect E2E stuff
|
||||
connect(this, &AccountSettings::requesetMnemonic, _accountState->account()->e2e(), &ClientSideEncryption::slotRequestMnemonic);
|
||||
connect(_accountState->account()->e2e(), &ClientSideEncryption::showMnemonic, this, &AccountSettings::slotShowMnemonic);
|
||||
|
||||
connect(_accountState->account()->e2e(), &ClientSideEncryption::mnemonicGenerated, this, &AccountSettings::slotNewMnemonicGenerated);
|
||||
if (_accountState->account()->e2e()->newMnemonicGenerated())
|
||||
{
|
||||
slotNewMnemonicGenerated();
|
||||
} else {
|
||||
ui->encryptionMessage->hide();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -222,6 +232,19 @@ void AccountSettings::createAccountToolbox()
|
|||
slotAccountAdded(_accountState);
|
||||
}
|
||||
|
||||
|
||||
void AccountSettings::slotNewMnemonicGenerated()
|
||||
{
|
||||
ui->encryptionMessage->setText(tr("This account supports end-to-end encryption"));
|
||||
|
||||
QAction *mnemonic = new QAction(tr("Enable encryption"), this);
|
||||
connect(mnemonic, &QAction::triggered, this, &AccountSettings::requesetMnemonic);
|
||||
connect(mnemonic, &QAction::triggered, ui->encryptionMessage, &KMessageWidget::hide);
|
||||
|
||||
ui->encryptionMessage->addAction(mnemonic);
|
||||
ui->encryptionMessage->show();
|
||||
}
|
||||
|
||||
void AccountSettings::slotMenuBeforeShow() {
|
||||
if (_menuShown) {
|
||||
return;
|
||||
|
@ -401,7 +424,7 @@ bool AccountSettings::canEncryptOrDecrypt (const FolderStatusModel::SubFolderInf
|
|||
return true;
|
||||
}
|
||||
|
||||
void AccountSettings::slotMarkSubfolderEncrpted(const FolderStatusModel::SubFolderInfo* folderInfo)
|
||||
void AccountSettings::slotMarkSubfolderEncrypted(const FolderStatusModel::SubFolderInfo* folderInfo)
|
||||
{
|
||||
if (!canEncryptOrDecrypt(folderInfo)) {
|
||||
return;
|
||||
|
@ -518,6 +541,51 @@ void AccountSettings::slotLockForDecryptionError(const QByteArray& fileId, int h
|
|||
qDebug() << "Error Locking for decryption";
|
||||
}
|
||||
|
||||
void AccountSettings::slotEditCurrentIgnoredFiles()
|
||||
{
|
||||
Folder *f = FolderMan::instance()->folder(selectedFolderAlias());
|
||||
if (f == nullptr)
|
||||
return;
|
||||
openIgnoredFilesDialog(f->path());
|
||||
}
|
||||
|
||||
void AccountSettings::slotEditCurrentLocalIgnoredFiles()
|
||||
{
|
||||
QModelIndex selected = ui->_folderList->selectionModel()->currentIndex();
|
||||
if (!selected.isValid() || _model->classify(selected) != FolderStatusModel::SubFolder)
|
||||
return;
|
||||
QString fileName = _model->data(selected, FolderStatusDelegate::FolderPathRole).toString();
|
||||
openIgnoredFilesDialog(fileName);
|
||||
}
|
||||
|
||||
void AccountSettings::openIgnoredFilesDialog(const QString & absFolderPath)
|
||||
{
|
||||
Q_ASSERT(absFolderPath.startsWith('/'));
|
||||
Q_ASSERT(absFolderPath.endsWith('/'));
|
||||
|
||||
const QString ignoreFile = absFolderPath + ".sync-exclude.lst";
|
||||
auto layout = new QVBoxLayout();
|
||||
auto ignoreListWidget = new IgnoreListTableWidget(this);
|
||||
ignoreListWidget->readIgnoreFile(ignoreFile);
|
||||
layout->addWidget(ignoreListWidget);
|
||||
|
||||
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
layout->addWidget(buttonBox);
|
||||
|
||||
auto dialog = new QDialog();
|
||||
dialog->setLayout(layout);
|
||||
|
||||
connect(buttonBox, &QDialogButtonBox::clicked, [=](QAbstractButton * button) {
|
||||
if (buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole)
|
||||
ignoreListWidget->slotWriteIgnoreFile(ignoreFile);
|
||||
dialog->close();
|
||||
});
|
||||
connect(buttonBox, &QDialogButtonBox::rejected,
|
||||
dialog, &QDialog::close);
|
||||
|
||||
dialog->open();
|
||||
}
|
||||
|
||||
void AccountSettings::slotSubfolderContextMenuRequested(const QModelIndex& index, const QPoint& pos)
|
||||
{
|
||||
Q_UNUSED(pos);
|
||||
|
@ -540,12 +608,16 @@ void AccountSettings::slotSubfolderContextMenuRequested(const QModelIndex& index
|
|||
|
||||
if (!isEncrypted) {
|
||||
ac = menu.addAction(tr("Encrypt"));
|
||||
connect(ac, &QAction::triggered, [this, &info] { slotMarkSubfolderEncrpted(info); });
|
||||
connect(ac, &QAction::triggered, [this, &info] { slotMarkSubfolderEncrypted(info); });
|
||||
} else {
|
||||
// Ingore decrypting for now since it only works with an empty folder
|
||||
// connect(ac, &QAction::triggered, [this, &info] { slotMarkSubfolderDecrypted(info); });
|
||||
}
|
||||
}
|
||||
|
||||
ac = menu.addAction(tr("Edit Ignored Files"));
|
||||
connect(ac, &QAction::triggered, this, &AccountSettings::slotEditCurrentLocalIgnoredFiles);
|
||||
|
||||
menu.exec(QCursor::pos());
|
||||
}
|
||||
|
||||
|
@ -579,6 +651,9 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos)
|
|||
QAction *ac = menu->addAction(tr("Open folder"));
|
||||
connect(ac, &QAction::triggered, this, &AccountSettings::slotOpenCurrentFolder);
|
||||
|
||||
ac = menu->addAction(tr("Edit Ignored Files"));
|
||||
connect(ac, &QAction::triggered, this, &AccountSettings::slotEditCurrentIgnoredFiles);
|
||||
|
||||
if (!ui->_folderList->isExpanded(index)) {
|
||||
ac = menu->addAction(tr("Choose what to sync"));
|
||||
ac->setEnabled(folderConnected);
|
||||
|
|
|
@ -80,6 +80,8 @@ protected slots:
|
|||
void slotRemoveCurrentFolder();
|
||||
void slotOpenCurrentFolder(); // sync folder
|
||||
void slotOpenCurrentLocalSubFolder(); // selected subfolder in sync folder
|
||||
void slotEditCurrentIgnoredFiles();
|
||||
void slotEditCurrentLocalIgnoredFiles();
|
||||
void slotFolderWizardAccepted();
|
||||
void slotFolderWizardRejected();
|
||||
void slotDeleteAccount();
|
||||
|
@ -87,7 +89,7 @@ protected slots:
|
|||
void slotOpenAccountWizard();
|
||||
void slotAccountAdded(AccountState *);
|
||||
void refreshSelectiveSyncStatus();
|
||||
void slotMarkSubfolderEncrpted(const FolderStatusModel::SubFolderInfo* folderInfo);
|
||||
void slotMarkSubfolderEncrypted(const FolderStatusModel::SubFolderInfo* folderInfo);
|
||||
void slotMarkSubfolderDecrypted(const FolderStatusModel::SubFolderInfo* folderInfo);
|
||||
void slotSubfolderContextMenuRequested(const QModelIndex& idx, const QPoint& point);
|
||||
void slotCustomContextMenuRequested(const QPoint &);
|
||||
|
@ -99,6 +101,7 @@ protected slots:
|
|||
|
||||
// Encryption Related Stuff.
|
||||
void slotShowMnemonic(const QString &mnemonic);
|
||||
void slotNewMnemonicGenerated();
|
||||
|
||||
void slotEncryptionFlagSuccess(const QByteArray &folderId);
|
||||
void slotEncryptionFlagError(const QByteArray &folderId, int httpReturnCode);
|
||||
|
@ -109,7 +112,7 @@ protected slots:
|
|||
void slotUploadMetadataSuccess(const QByteArray& folderId);
|
||||
void slotUpdateMetadataError(const QByteArray& folderId, int httpReturnCode);
|
||||
|
||||
// Remove Encryotion Bit.
|
||||
// Remove Encryption Bit.
|
||||
void slotLockForDecryptionSuccess(const QByteArray& folderId, const QByteArray& token);
|
||||
void slotLockForDecryptionError(const QByteArray& folderId, int httpReturnCode);
|
||||
void slotDeleteMetadataSuccess(const QByteArray& folderId);
|
||||
|
@ -124,6 +127,7 @@ private:
|
|||
QStringList errors = QStringList());
|
||||
bool event(QEvent *) override;
|
||||
void createAccountToolbox();
|
||||
void openIgnoredFilesDialog(const QString & absFolderPath);
|
||||
|
||||
/// Returns the alias of the selected folder, empty string if none
|
||||
QString selectedFolderAlias() const;
|
||||
|
|
|
@ -6,136 +6,15 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>575</width>
|
||||
<width>582</width>
|
||||
<height>557</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
<string notr="true">Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QWidget" name="accountStatus" native="true">
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="SslButton" name="sslButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="connectLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Connected with <server> as <user></string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QToolButton" name="_accountToolbox">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<layout class="QHBoxLayout" name="storageGroupBox">
|
||||
<item>
|
||||
<widget class="QLabel" name="quotaInfoLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Storage space: ...</string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="quotaProgressBar">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>7</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>-1</number>
|
||||
</property>
|
||||
<property name="textVisible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="OCC::FolderStatusView" name="_folderList">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>5</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::CustomContextMenu</enum>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="animated">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<item row="4" column="0">
|
||||
<widget class="QWidget" name="selectiveSyncStatus" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
|
@ -267,6 +146,130 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QWidget" name="accountStatus" native="true">
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="SslButton" name="sslButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="connectLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Connected with <server> as <user></string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QToolButton" name="_accountToolbox">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<layout class="QHBoxLayout" name="storageGroupBox">
|
||||
<item>
|
||||
<widget class="QLabel" name="quotaInfoLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Storage space: ...</string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="quotaProgressBar">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>7</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>-1</number>
|
||||
</property>
|
||||
<property name="textVisible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="OCC::FolderStatusView" name="_folderList">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>5</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::CustomContextMenu</enum>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="animated">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="KMessageWidget" name="encryptionMessage" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
|
@ -280,6 +283,12 @@
|
|||
<extends>QTreeView</extends>
|
||||
<header>folderstatusview.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>KMessageWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>kmessagewidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
|
|
@ -103,10 +103,12 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
|
|||
|| a._status == SyncFileItem::BlacklistedError) {
|
||||
return QIcon(QLatin1String(":/client/resources/state-error.svg"));
|
||||
} else if(a._status == SyncFileItem::SoftError
|
||||
|| a._status == SyncFileItem::FileIgnored
|
||||
|| a._status == SyncFileItem::Conflict
|
||||
|| a._status == SyncFileItem::Restoration){
|
||||
|| a._status == SyncFileItem::Restoration
|
||||
|| a._status == SyncFileItem::FileLocked){
|
||||
return QIcon(QLatin1String(":/client/resources/state-warning.svg"));
|
||||
} else if(a._status == SyncFileItem::FileIgnored){
|
||||
return QIcon(QLatin1String(":/client/resources/state-info.svg"));
|
||||
}
|
||||
return QIcon(QLatin1String(":/client/resources/state-sync.svg"));
|
||||
}
|
||||
|
|
|
@ -121,6 +121,11 @@ void ActivityWidget::slotProgressInfo(const QString &folder, const ProgressInfo
|
|||
continue;
|
||||
}
|
||||
|
||||
if(activity._status == SyncFileItem::FileLocked && !QFileInfo(f->path() + activity._file).exists()){
|
||||
_model->removeActivityFromActivityList(activity);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if(activity._status == SyncFileItem::FileIgnored && !QFileInfo(f->path() + activity._file).exists()){
|
||||
_model->removeActivityFromActivityList(activity);
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
<string notr="true">Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="sizeConstraint">
|
||||
|
@ -81,7 +81,7 @@
|
|||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
<string notr="true">TextLabel</string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
|
|
|
@ -24,7 +24,7 @@ extern "C" {
|
|||
|
||||
CloudProvidersProviderExporter *_providerExporter;
|
||||
|
||||
void on_bus_acquired (GDBusConnection *connection, const gchar *name, gpointer user_data)
|
||||
void on_name_acquired (GDBusConnection *connection, const gchar *name, gpointer user_data)
|
||||
{
|
||||
Q_UNUSED(name);
|
||||
CloudProviderManager *self;
|
||||
|
@ -34,6 +34,14 @@ void on_bus_acquired (GDBusConnection *connection, const gchar *name, gpointer u
|
|||
self->registerSignals();
|
||||
}
|
||||
|
||||
void on_name_lost (GDBusConnection *connection, const gchar *name, gpointer user_data)
|
||||
{
|
||||
Q_UNUSED(connection);
|
||||
Q_UNUSED(name);
|
||||
Q_UNUSED(user_data);
|
||||
g_clear_object (&_providerExporter);
|
||||
}
|
||||
|
||||
void CloudProviderManager::registerSignals()
|
||||
{
|
||||
OCC::FolderMan *folderManager = OCC::FolderMan::instance();
|
||||
|
@ -45,7 +53,7 @@ CloudProviderManager::CloudProviderManager(QObject *parent) : QObject(parent)
|
|||
{
|
||||
_map = new QMap<QString, CloudProviderWrapper*>();
|
||||
QString busName = QString(LIBCLOUDPROVIDERS_DBUS_BUS_NAME);
|
||||
g_bus_own_name (G_BUS_TYPE_SESSION, busName.toAscii().data(), G_BUS_NAME_OWNER_FLAGS_NONE, on_bus_acquired, nullptr, nullptr, this, nullptr);
|
||||
g_bus_own_name (G_BUS_TYPE_SESSION, busName.toAscii().data(), G_BUS_NAME_OWNER_FLAGS_NONE, nullptr, on_name_acquired, nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
void CloudProviderManager::slotFolderListChanged(const Folder::Map &folderMap)
|
||||
|
|
|
@ -33,33 +33,25 @@ using namespace OCC;
|
|||
|
||||
GSimpleActionGroup *actionGroup = nullptr;
|
||||
|
||||
static
|
||||
gchar* qstring_to_gchar(const QString &string)
|
||||
{
|
||||
QByteArray ba = string.toUtf8();
|
||||
char* data = ba.data();
|
||||
return g_strdup(data);
|
||||
}
|
||||
|
||||
CloudProviderWrapper::CloudProviderWrapper(QObject *parent, Folder *folder, CloudProvidersProviderExporter* cloudprovider) : QObject(parent)
|
||||
, _folder(folder)
|
||||
{
|
||||
GMenuModel *model;
|
||||
GActionGroup *action_group;
|
||||
_recentlyChanged = new QList<QPair<QString, QString>>();
|
||||
gchar *accountName = g_strdup_printf ("Account%sFolder%s",
|
||||
qstring_to_gchar(folder->alias()),
|
||||
qstring_to_gchar(folder->accountState()->account()->id()));
|
||||
QString accountName = QString("Account%1Folder%2").arg(folder->alias(), folder->accountState()->account()->id());
|
||||
|
||||
_cloudProvider = CLOUD_PROVIDERS_PROVIDER_EXPORTER(cloudprovider);
|
||||
_cloudProviderAccount = cloud_providers_account_exporter_new(_cloudProvider, accountName);
|
||||
_cloudProviderAccount = cloud_providers_account_exporter_new(_cloudProvider, accountName.toUtf8().data());
|
||||
|
||||
gchar* folderName = qstring_to_gchar(folder->shortGuiLocalPath());
|
||||
gchar* folderPath = qstring_to_gchar(folder->cleanPath());
|
||||
cloud_providers_account_exporter_set_name (_cloudProviderAccount, folderName);
|
||||
cloud_providers_account_exporter_set_name (_cloudProviderAccount, folder->shortGuiLocalPath().toUtf8().data());
|
||||
cloud_providers_account_exporter_set_icon (_cloudProviderAccount, g_icon_new_for_string(APPLICATION_ICON_NAME, nullptr));
|
||||
cloud_providers_account_exporter_set_path (_cloudProviderAccount, folderPath);
|
||||
cloud_providers_account_exporter_set_path (_cloudProviderAccount, folder->cleanPath().toUtf8().data());
|
||||
cloud_providers_account_exporter_set_status (_cloudProviderAccount, CLOUD_PROVIDERS_ACCOUNT_STATUS_IDLE);
|
||||
cloud_providers_account_exporter_set_menu_model (_cloudProviderAccount, getMenuModel());
|
||||
cloud_providers_account_exporter_set_action_group (_cloudProviderAccount, getActionGroup());
|
||||
model = getMenuModel();
|
||||
cloud_providers_account_exporter_set_menu_model (_cloudProviderAccount, model);
|
||||
action_group = getActionGroup();
|
||||
cloud_providers_account_exporter_set_action_group (_cloudProviderAccount, action_group);
|
||||
|
||||
connect(ProgressDispatcher::instance(), SIGNAL(progressInfo(QString, ProgressInfo)), this, SLOT(slotUpdateProgress(QString, ProgressInfo)));
|
||||
connect(_folder, SIGNAL(syncStarted()), this, SLOT(slotSyncStarted()));
|
||||
|
@ -68,10 +60,8 @@ CloudProviderWrapper::CloudProviderWrapper(QObject *parent, Folder *folder, Clou
|
|||
|
||||
_paused = _folder->syncPaused();
|
||||
updatePauseStatus();
|
||||
|
||||
g_free(accountName);
|
||||
g_free(folderName);
|
||||
g_free(folderPath);
|
||||
g_clear_object (&model);
|
||||
g_clear_object (&action_group);
|
||||
}
|
||||
|
||||
CloudProviderWrapper::~CloudProviderWrapper()
|
||||
|
@ -93,6 +83,16 @@ static bool shouldShowInRecentsMenu(const SyncFileItem &item)
|
|||
&& item._instruction != CSYNC_INSTRUCTION_NONE;
|
||||
}
|
||||
|
||||
static GMenuItem *menu_item_new(const QString &label, const gchar *detailed_action)
|
||||
{
|
||||
return g_menu_item_new(label.toUtf8 ().data(), detailed_action);
|
||||
}
|
||||
|
||||
static GMenuItem *menu_item_new_submenu(const QString &label, GMenuModel *submenu)
|
||||
{
|
||||
return g_menu_item_new_submenu(label.toUtf8 ().data(), submenu);
|
||||
}
|
||||
|
||||
void CloudProviderWrapper::slotUpdateProgress(const QString &folder, const ProgressInfo &progress)
|
||||
{
|
||||
// Only update progress for the current folder
|
||||
|
@ -154,30 +154,25 @@ void CloudProviderWrapper::slotUpdateProgress(const QString &folder, const Progr
|
|||
if(!_recentlyChanged->isEmpty()) {
|
||||
QList<QPair<QString, QString>>::iterator i;
|
||||
for (i = _recentlyChanged->begin(); i != _recentlyChanged->end(); i++) {
|
||||
gchar *file;
|
||||
QString label = i->first;
|
||||
QString fullPath = i->second;
|
||||
file = g_strdup(qstring_to_gchar(label));
|
||||
item = g_menu_item_new(file, "cloudprovider.showfile");
|
||||
g_menu_item_set_action_and_target_value(item, "cloudprovider.showfile", g_variant_new_string(qstring_to_gchar(fullPath)));
|
||||
item = menu_item_new(label, "cloudprovider.showfile");
|
||||
g_menu_item_set_action_and_target_value(item, "cloudprovider.showfile", g_variant_new_string(fullPath.toUtf8().data()));
|
||||
g_menu_append_item(_recentMenu, item);
|
||||
g_clear_object (&item);
|
||||
}
|
||||
} else {
|
||||
item = g_menu_item_new("No recently changed files", nullptr);
|
||||
item = menu_item_new(tr("No recently changed files"), nullptr);
|
||||
g_menu_append_item(_recentMenu, item);
|
||||
g_clear_object (&item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CloudProviderWrapper::updateStatusText(QString statusText)
|
||||
{
|
||||
char* state = qstring_to_gchar(_folder->accountState()->stateString(_folder->accountState()->state()));
|
||||
char* statusChar = qstring_to_gchar(statusText);
|
||||
char* status = g_strdup_printf("%s - %s", state, statusChar);
|
||||
cloud_providers_account_exporter_set_status_details(_cloudProviderAccount, status);
|
||||
g_free(state);
|
||||
g_free(statusChar);
|
||||
g_free(status);
|
||||
QString status = QString("%1 - %2").arg(_folder->accountState()->stateString(_folder->accountState()->state()), statusText);
|
||||
cloud_providers_account_exporter_set_status_details(_cloudProviderAccount, status.toUtf8().data());
|
||||
}
|
||||
|
||||
void CloudProviderWrapper::updatePauseStatus()
|
||||
|
@ -217,38 +212,53 @@ GMenuModel* CloudProviderWrapper::getMenuModel() {
|
|||
|
||||
GMenu* section;
|
||||
GMenuItem* item;
|
||||
QString item_label;
|
||||
|
||||
_mainMenu = g_menu_new();
|
||||
|
||||
section = g_menu_new();
|
||||
item = g_menu_item_new("Open website", "cloudprovider.openwebsite");
|
||||
item = menu_item_new(tr("Open website"), "cloudprovider.openwebsite");
|
||||
g_menu_append_item(section, item);
|
||||
g_clear_object (&item);
|
||||
g_menu_append_section(_mainMenu, nullptr, G_MENU_MODEL(section));
|
||||
g_clear_object (§ion);
|
||||
|
||||
_recentMenu = g_menu_new();
|
||||
item = g_menu_item_new("No recently changed files", nullptr);
|
||||
item = menu_item_new(tr("No recently changed files"), nullptr);
|
||||
g_menu_append_item(_recentMenu, item);
|
||||
section = g_menu_new();
|
||||
item = g_menu_item_new_submenu("Recently changed", G_MENU_MODEL(_recentMenu));
|
||||
g_menu_append_item(section, item);
|
||||
g_menu_append_section(_mainMenu, nullptr, G_MENU_MODEL(section));
|
||||
g_clear_object (&item);
|
||||
|
||||
section = g_menu_new();
|
||||
item = g_menu_item_new("Pause synchronization", "cloudprovider.pause");
|
||||
item = menu_item_new_submenu(tr("Recently changed"), G_MENU_MODEL(_recentMenu));
|
||||
g_menu_append_item(section, item);
|
||||
g_clear_object (&item);
|
||||
g_menu_append_section(_mainMenu, nullptr, G_MENU_MODEL(section));
|
||||
g_clear_object (§ion);
|
||||
|
||||
section = g_menu_new();
|
||||
item = g_menu_item_new("Help", "cloudprovider.openhelp");
|
||||
g_menu_append_item(section, item);
|
||||
item = g_menu_item_new("Settings", "cloudprovider.opensettings");
|
||||
g_menu_append_item(section, item);
|
||||
item = g_menu_item_new("Log out", "cloudprovider.logout");
|
||||
g_menu_append_item(section, item);
|
||||
item = g_menu_item_new("Quit sync client", "cloudprovider.quit");
|
||||
item = menu_item_new(tr("Pause synchronization"), "cloudprovider.pause");
|
||||
g_menu_append_item(section, item);
|
||||
g_clear_object (&item);
|
||||
g_menu_append_section(_mainMenu, nullptr, G_MENU_MODEL(section));
|
||||
g_clear_object (§ion);
|
||||
|
||||
section = g_menu_new();
|
||||
item = menu_item_new(tr("Help"), "cloudprovider.openhelp");
|
||||
g_menu_append_item(section, item);
|
||||
g_clear_object (&item);
|
||||
item = menu_item_new(tr("Settings"), "cloudprovider.opensettings");
|
||||
g_menu_append_item(section, item);
|
||||
g_clear_object (&item);
|
||||
item = menu_item_new(tr("Log out"), "cloudprovider.logout");
|
||||
g_menu_append_item(section, item);
|
||||
g_clear_object (&item);
|
||||
item = menu_item_new(tr("Quit sync client"), "cloudprovider.quit");
|
||||
g_menu_append_item(section, item);
|
||||
g_clear_object (&item);
|
||||
g_menu_append_section(_mainMenu, nullptr, G_MENU_MODEL(section));
|
||||
g_clear_object (§ion);
|
||||
|
||||
g_clear_object (&_recentMenu);
|
||||
return G_MENU_MODEL(_mainMenu);
|
||||
}
|
||||
|
||||
|
@ -277,11 +287,10 @@ activate_action_open (GSimpleAction *action, GVariant *parameter, gpointer user_
|
|||
}
|
||||
|
||||
if(g_str_equal(name, "showfile")) {
|
||||
gchar *path;
|
||||
g_variant_get (parameter, "s", &path);
|
||||
g_print("showfile => %s\n", path);
|
||||
showInFileManager(QString(path));
|
||||
}
|
||||
const gchar *path = g_variant_get_string (parameter, NULL);
|
||||
g_print("showfile => %s\n", path);
|
||||
showInFileManager(QString(path));
|
||||
}
|
||||
|
||||
if(g_str_equal(name, "logout")) {
|
||||
self->folder()->accountState()->signOutByUi();
|
||||
|
@ -331,12 +340,13 @@ static GActionEntry actions[] = {
|
|||
|
||||
GActionGroup* CloudProviderWrapper::getActionGroup()
|
||||
{
|
||||
g_clear_object (&actionGroup);
|
||||
actionGroup = g_simple_action_group_new ();
|
||||
g_action_map_add_action_entries (G_ACTION_MAP (actionGroup), actions, G_N_ELEMENTS (actions), this);
|
||||
bool state = _folder->syncPaused();
|
||||
GAction *pause = g_action_map_lookup_action(G_ACTION_MAP(actionGroup), "pause");
|
||||
g_simple_action_set_state(G_SIMPLE_ACTION(pause), g_variant_new_boolean(state));
|
||||
return G_ACTION_GROUP (actionGroup);
|
||||
return G_ACTION_GROUP (g_object_ref (actionGroup));
|
||||
}
|
||||
|
||||
void CloudProviderWrapper::slotSyncPausedChanged(Folder *folder, bool state)
|
||||
|
|
176
src/gui/creds/flow2auth.cpp
Normal file
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* Copyright (C) by Olivier Goffart <ogoffart@woboq.com>
|
||||
* Copyright (C) by Michael Schuster <michael@nextcloud.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QTimer>
|
||||
#include <QBuffer>
|
||||
#include "account.h"
|
||||
#include "creds/flow2auth.h"
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
#include "theme.h"
|
||||
#include "networkjobs.h"
|
||||
#include "configfile.h"
|
||||
|
||||
namespace OCC {
|
||||
|
||||
Q_LOGGING_CATEGORY(lcFlow2auth, "nextcloud.sync.credentials.flow2auth", QtInfoMsg)
|
||||
|
||||
Flow2Auth::~Flow2Auth()
|
||||
{
|
||||
}
|
||||
|
||||
void Flow2Auth::start()
|
||||
{
|
||||
// Note: All startup code is in openBrowser() to allow reinitiate a new request with
|
||||
// fresh tokens. Opening the same pollEndpoint link twice triggers an expiration
|
||||
// message by the server (security, intended design).
|
||||
openBrowser();
|
||||
}
|
||||
|
||||
QUrl Flow2Auth::authorisationLink() const
|
||||
{
|
||||
return _loginUrl;
|
||||
}
|
||||
|
||||
void Flow2Auth::openBrowser()
|
||||
{
|
||||
_pollTimer.stop();
|
||||
|
||||
// Step 1: Initiate a login, do an anonymous POST request
|
||||
QUrl url = Utility::concatUrlPath(_account->url().toString(), QLatin1String("/index.php/login/v2"));
|
||||
|
||||
auto job = _account->sendRequest("POST", url);
|
||||
job->setTimeout(qMin(30 * 1000ll, job->timeoutMsec()));
|
||||
|
||||
QObject::connect(job, &SimpleNetworkJob::finishedSignal, this, [this](QNetworkReply *reply) {
|
||||
auto jsonData = reply->readAll();
|
||||
QJsonParseError jsonParseError;
|
||||
QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object();
|
||||
|
||||
QString pollToken = json.value("poll").toObject().value("token").toString();
|
||||
QString pollEndpoint = json.value("poll").toObject().value("endpoint").toString();
|
||||
QUrl loginUrl = json["login"].toString();
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError || jsonParseError.error != QJsonParseError::NoError
|
||||
|| json.isEmpty() || pollToken.isEmpty() || pollEndpoint.isEmpty() || loginUrl.isEmpty()) {
|
||||
QString errorReason;
|
||||
QString errorFromJson = json["error"].toString();
|
||||
if (!errorFromJson.isEmpty()) {
|
||||
errorReason = tr("Error returned from the server: <em>%1</em>")
|
||||
.arg(errorFromJson.toHtmlEscaped());
|
||||
} else if (reply->error() != QNetworkReply::NoError) {
|
||||
errorReason = tr("There was an error accessing the 'token' endpoint: <br><em>%1</em>")
|
||||
.arg(reply->errorString().toHtmlEscaped());
|
||||
} else if (jsonParseError.error != QJsonParseError::NoError) {
|
||||
errorReason = tr("Could not parse the JSON returned from the server: <br><em>%1</em>")
|
||||
.arg(jsonParseError.errorString());
|
||||
} else {
|
||||
errorReason = tr("The reply from the server did not contain all expected fields");
|
||||
}
|
||||
qCWarning(lcFlow2auth) << "Error when getting the loginUrl" << json << errorReason;
|
||||
emit result(Error);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
_loginUrl = loginUrl;
|
||||
_pollToken = pollToken;
|
||||
_pollEndpoint = pollEndpoint;
|
||||
|
||||
|
||||
// Start polling
|
||||
ConfigFile cfg;
|
||||
std::chrono::milliseconds polltime = cfg.remotePollInterval();
|
||||
qCInfo(lcFlow2auth) << "setting remote poll timer interval to" << polltime.count() << "msec";
|
||||
_pollTimer.setInterval(polltime.count());
|
||||
QObject::connect(&_pollTimer, &QTimer::timeout, this, &Flow2Auth::slotPollTimerTimeout);
|
||||
_pollTimer.start();
|
||||
|
||||
|
||||
// Try to open Browser
|
||||
if (!QDesktopServices::openUrl(authorisationLink())) {
|
||||
// We cannot open the browser, then we claim we don't support Flow2Auth.
|
||||
// Our UI callee should ask the user to copy and open the link.
|
||||
emit result(NotSupported, QString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Flow2Auth::slotPollTimerTimeout()
|
||||
{
|
||||
_pollTimer.stop();
|
||||
|
||||
// Step 2: Poll
|
||||
QNetworkRequest req;
|
||||
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
|
||||
auto requestBody = new QBuffer;
|
||||
QUrlQuery arguments(QString("token=%1").arg(_pollToken));
|
||||
requestBody->setData(arguments.query(QUrl::FullyEncoded).toLatin1());
|
||||
|
||||
auto job = _account->sendRequest("POST", _pollEndpoint, req, requestBody);
|
||||
job->setTimeout(qMin(30 * 1000ll, job->timeoutMsec()));
|
||||
|
||||
QObject::connect(job, &SimpleNetworkJob::finishedSignal, this, [this](QNetworkReply *reply) {
|
||||
auto jsonData = reply->readAll();
|
||||
QJsonParseError jsonParseError;
|
||||
QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object();
|
||||
|
||||
QUrl serverUrl = json["server"].toString();
|
||||
QString loginName = json["loginName"].toString();
|
||||
QString appPassword = json["appPassword"].toString();
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError || jsonParseError.error != QJsonParseError::NoError
|
||||
|| json.isEmpty() || serverUrl.isEmpty() || loginName.isEmpty() || appPassword.isEmpty()) {
|
||||
QString errorReason;
|
||||
QString errorFromJson = json["error"].toString();
|
||||
if (!errorFromJson.isEmpty()) {
|
||||
errorReason = tr("Error returned from the server: <em>%1</em>")
|
||||
.arg(errorFromJson.toHtmlEscaped());
|
||||
} else if (reply->error() != QNetworkReply::NoError) {
|
||||
errorReason = tr("There was an error accessing the 'token' endpoint: <br><em>%1</em>")
|
||||
.arg(reply->errorString().toHtmlEscaped());
|
||||
} else if (jsonParseError.error != QJsonParseError::NoError) {
|
||||
errorReason = tr("Could not parse the JSON returned from the server: <br><em>%1</em>")
|
||||
.arg(jsonParseError.errorString());
|
||||
} else {
|
||||
errorReason = tr("The reply from the server did not contain all expected fields");
|
||||
}
|
||||
qCDebug(lcFlow2auth) << "Error when polling for the appPassword" << json << errorReason;
|
||||
|
||||
// Forget sensitive data
|
||||
appPassword.clear();
|
||||
loginName.clear();
|
||||
|
||||
// Failed: poll again
|
||||
_pollTimer.start();
|
||||
return;
|
||||
}
|
||||
|
||||
// Success
|
||||
qCInfo(lcFlow2auth) << "Success getting the appPassword for user: " << loginName << ", server: " << serverUrl.toString();
|
||||
|
||||
_account->setUrl(serverUrl);
|
||||
|
||||
emit result(LoggedIn, loginName, appPassword);
|
||||
|
||||
// Forget sensitive data
|
||||
appPassword.clear();
|
||||
loginName.clear();
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace OCC
|
68
src/gui/creds/flow2auth.h
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright (C) by Olivier Goffart <ogoffart@woboq.com>
|
||||
* Copyright (C) by Michael Schuster <michael@nextcloud.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <QPointer>
|
||||
#include <QUrl>
|
||||
#include <QTimer>
|
||||
#include "accountfwd.h"
|
||||
|
||||
namespace OCC {
|
||||
|
||||
/**
|
||||
* Job that does the authorization, grants and fetches the access token via Login Flow v2
|
||||
*
|
||||
* See: https://docs.nextcloud.com/server/latest/developer_manual/client_apis/LoginFlow/index.html#login-flow-v2
|
||||
*
|
||||
*/
|
||||
class Flow2Auth : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Flow2Auth(Account *account, QObject *parent)
|
||||
: QObject(parent)
|
||||
, _account(account)
|
||||
{
|
||||
}
|
||||
~Flow2Auth();
|
||||
|
||||
enum Result { NotSupported,
|
||||
LoggedIn,
|
||||
Error };
|
||||
Q_ENUM(Result);
|
||||
void start();
|
||||
void openBrowser();
|
||||
QUrl authorisationLink() const;
|
||||
|
||||
signals:
|
||||
/**
|
||||
* The state has changed.
|
||||
* when logged in, appPassword has the value of the app password.
|
||||
*/
|
||||
void result(Flow2Auth::Result result, const QString &user = QString(), const QString &appPassword = QString());
|
||||
|
||||
private slots:
|
||||
void slotPollTimerTimeout();
|
||||
|
||||
private:
|
||||
Account *_account;
|
||||
QUrl _loginUrl;
|
||||
QString _pollToken;
|
||||
QString _pollEndpoint;
|
||||
QTimer _pollTimer;
|
||||
};
|
||||
|
||||
|
||||
} // namespace OCC
|
|
@ -90,7 +90,7 @@ ShibbolethWebView::ShibbolethWebView(AccountPtr account, QWidget *parent)
|
|||
QWebView *debugView = new QWebView(this);
|
||||
debugView->setPage(debugPage);
|
||||
QMainWindow *window = new QMainWindow(this);
|
||||
window->setWindowTitle(tr("SSL Chipher Debug View"));
|
||||
window->setWindowTitle(tr("SSL Cipher Debug View"));
|
||||
window->setCentralWidget(debugView);
|
||||
window->show();
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include "accessmanager.h"
|
||||
#include "account.h"
|
||||
#include "configfile.h"
|
||||
#include "theme.h"
|
||||
#include "wizard/webview.h"
|
||||
#include "webflowcredentialsdialog.h"
|
||||
|
@ -24,6 +25,13 @@ namespace OCC {
|
|||
|
||||
Q_LOGGING_CATEGORY(lcWebFlowCredentials, "sync.credentials.webflow", QtInfoMsg)
|
||||
|
||||
namespace {
|
||||
const char userC[] = "user";
|
||||
const char clientCertificatePEMC[] = "_clientCertificatePEM";
|
||||
const char clientKeyPEMC[] = "_clientKeyPEM";
|
||||
const char clientCaCertificatePEMC[] = "_clientCaCertificatePEM";
|
||||
} // ns
|
||||
|
||||
class WebFlowCredentialsAccessManager : public AccessManager
|
||||
{
|
||||
public:
|
||||
|
@ -37,13 +45,27 @@ protected:
|
|||
QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData) override
|
||||
{
|
||||
QNetworkRequest req(request);
|
||||
if (!req.attribute(HttpCredentials::DontAddCredentialsAttribute).toBool()) {
|
||||
if (!req.attribute(WebFlowCredentials::DontAddCredentialsAttribute).toBool()) {
|
||||
if (_cred && !_cred->password().isEmpty()) {
|
||||
QByteArray credHash = QByteArray(_cred->user().toUtf8() + ":" + _cred->password().toUtf8()).toBase64();
|
||||
req.setRawHeader("Authorization", "Basic " + credHash);
|
||||
}
|
||||
}
|
||||
|
||||
if (_cred && !_cred->_clientSslKey.isNull() && !_cred->_clientSslCertificate.isNull()) {
|
||||
// SSL configuration
|
||||
QSslConfiguration sslConfiguration = req.sslConfiguration();
|
||||
sslConfiguration.setLocalCertificate(_cred->_clientSslCertificate);
|
||||
sslConfiguration.setPrivateKey(_cred->_clientSslKey);
|
||||
|
||||
// Merge client side CA with system CA
|
||||
auto ca = sslConfiguration.systemCaCertificates();
|
||||
ca.append(_cred->_clientSslCaCertificates);
|
||||
sslConfiguration.setCaCertificates(ca);
|
||||
|
||||
req.setSslConfiguration(sslConfiguration);
|
||||
}
|
||||
|
||||
return AccessManager::createRequest(op, req, outgoingData);
|
||||
}
|
||||
|
||||
|
@ -53,22 +75,33 @@ private:
|
|||
QPointer<const WebFlowCredentials> _cred;
|
||||
};
|
||||
|
||||
static void addSettingsToJob(Account *account, QKeychain::Job *job)
|
||||
{
|
||||
Q_UNUSED(account)
|
||||
auto settings = ConfigFile::settingsWithGroup(Theme::instance()->appName());
|
||||
settings->setParent(job); // make the job parent to make setting deleted properly
|
||||
job->setSettings(settings.release());
|
||||
}
|
||||
|
||||
WebFlowCredentials::WebFlowCredentials()
|
||||
: _ready(false)
|
||||
, _credentialsValid(false)
|
||||
, _keychainMigration(false)
|
||||
, _retryOnKeyChainError(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
WebFlowCredentials::WebFlowCredentials(const QString &user, const QString &password, const QSslCertificate &certificate, const QSslKey &key)
|
||||
WebFlowCredentials::WebFlowCredentials(const QString &user, const QString &password, const QSslCertificate &certificate, const QSslKey &key, const QList<QSslCertificate> &caCertificates)
|
||||
: _user(user)
|
||||
, _password(password)
|
||||
, _clientSslKey(key)
|
||||
, _clientSslCertificate(certificate)
|
||||
, _clientSslCaCertificates(caCertificates)
|
||||
, _ready(true)
|
||||
, _credentialsValid(true)
|
||||
, _keychainMigration(false)
|
||||
, _retryOnKeyChainError(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
@ -102,24 +135,29 @@ bool WebFlowCredentials::ready() const {
|
|||
void WebFlowCredentials::fetchFromKeychain() {
|
||||
_wasFetched = true;
|
||||
|
||||
// Make sure we get the user fromt he config file
|
||||
// Make sure we get the user from the config file
|
||||
fetchUser();
|
||||
|
||||
if (ready()) {
|
||||
emit fetched();
|
||||
} else {
|
||||
qCInfo(lcWebFlowCredentials()) << "Fetch from keyhchain!";
|
||||
qCInfo(lcWebFlowCredentials()) << "Fetch from keychain!";
|
||||
fetchFromKeychainHelper();
|
||||
}
|
||||
}
|
||||
|
||||
void WebFlowCredentials::askFromUser() {
|
||||
_askDialog = new WebFlowCredentialsDialog();
|
||||
// LoginFlowV2 > WebViewFlow > OAuth > Shib > Basic
|
||||
bool useFlow2 = (_account->serverVersionInt() >= Account::makeServerVersion(16, 0, 0));
|
||||
|
||||
QUrl url = _account->url();
|
||||
QString path = url.path() + "/index.php/login/flow";
|
||||
url.setPath(path);
|
||||
_askDialog->setUrl(url);
|
||||
_askDialog = new WebFlowCredentialsDialog(_account, useFlow2);
|
||||
|
||||
if (!useFlow2) {
|
||||
QUrl url = _account->url();
|
||||
QString path = url.path() + "/index.php/login/flow";
|
||||
url.setPath(path);
|
||||
_askDialog->setUrl(url);
|
||||
}
|
||||
|
||||
QString msg = tr("You have been logged out of %1 as user %2. Please login again")
|
||||
.arg(_account->displayName(), _user);
|
||||
|
@ -133,7 +171,7 @@ void WebFlowCredentials::askFromUser() {
|
|||
}
|
||||
|
||||
void WebFlowCredentials::slotAskFromUserCredentialsProvided(const QString &user, const QString &pass, const QString &host) {
|
||||
Q_UNUSED(host);
|
||||
Q_UNUSED(host)
|
||||
|
||||
if (_user != user) {
|
||||
qCInfo(lcWebFlowCredentials()) << "Authed with the wrong user!";
|
||||
|
@ -142,10 +180,12 @@ void WebFlowCredentials::slotAskFromUserCredentialsProvided(const QString &user,
|
|||
.arg(_user);
|
||||
_askDialog->setError(msg);
|
||||
|
||||
QUrl url = _account->url();
|
||||
QString path = url.path() + "/index.php/login/flow";
|
||||
url.setPath(path);
|
||||
_askDialog->setUrl(url);
|
||||
if (!_askDialog->isUsingFlow2()) {
|
||||
QUrl url = _account->url();
|
||||
QString path = url.path() + "/index.php/login/flow";
|
||||
url.setPath(path);
|
||||
_askDialog->setUrl(url);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -178,17 +218,128 @@ void WebFlowCredentials::persist() {
|
|||
return;
|
||||
}
|
||||
|
||||
_account->setCredentialSetting("user", _user);
|
||||
_account->setCredentialSetting(userC, _user);
|
||||
_account->wantsAccountSaved(_account);
|
||||
|
||||
//TODO: Add ssl cert and key storing
|
||||
// write cert if there is one
|
||||
if (!_clientSslCertificate.isNull()) {
|
||||
WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
|
||||
addSettingsToJob(_account, job);
|
||||
job->setInsecureFallback(false);
|
||||
connect(job, &Job::finished, this, &WebFlowCredentials::slotWriteClientCertPEMJobDone);
|
||||
job->setKey(keychainKey(_account->url().toString(), _user + clientCertificatePEMC, _account->id()));
|
||||
job->setBinaryData(_clientSslCertificate.toPem());
|
||||
job->start();
|
||||
} else {
|
||||
// no cert, just write credentials
|
||||
slotWriteClientCertPEMJobDone();
|
||||
}
|
||||
}
|
||||
|
||||
void WebFlowCredentials::slotWriteClientCertPEMJobDone()
|
||||
{
|
||||
// write ssl key if there is one
|
||||
if (!_clientSslKey.isNull()) {
|
||||
WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
|
||||
addSettingsToJob(_account, job);
|
||||
job->setInsecureFallback(false);
|
||||
connect(job, &Job::finished, this, &WebFlowCredentials::slotWriteClientKeyPEMJobDone);
|
||||
job->setKey(keychainKey(_account->url().toString(), _user + clientKeyPEMC, _account->id()));
|
||||
job->setBinaryData(_clientSslKey.toPem());
|
||||
job->start();
|
||||
} else {
|
||||
// no key, just write credentials
|
||||
slotWriteClientKeyPEMJobDone();
|
||||
}
|
||||
}
|
||||
|
||||
void WebFlowCredentials::writeSingleClientCaCertPEM()
|
||||
{
|
||||
// write a ca cert if there is any in the queue
|
||||
if (!_clientSslCaCertificatesWriteQueue.isEmpty()) {
|
||||
// grab and remove the first cert from the queue
|
||||
auto cert = _clientSslCaCertificatesWriteQueue.dequeue();
|
||||
|
||||
auto index = (_clientSslCaCertificates.count() - _clientSslCaCertificatesWriteQueue.count()) - 1;
|
||||
|
||||
// keep the limit
|
||||
if (index > (_clientSslCaCertificatesMaxCount - 1)) {
|
||||
qCWarning(lcWebFlowCredentials) << "Maximum client CA cert count exceeded while writing slot" << QString::number(index) << "), cutting off after " << QString::number(_clientSslCaCertificatesMaxCount) << "certs";
|
||||
|
||||
_clientSslCaCertificatesWriteQueue.clear();
|
||||
|
||||
slotWriteClientCaCertsPEMJobDone(nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
|
||||
addSettingsToJob(_account, job);
|
||||
job->setInsecureFallback(false);
|
||||
connect(job, &Job::finished, this, &WebFlowCredentials::slotWriteClientCaCertsPEMJobDone);
|
||||
job->setKey(keychainKey(_account->url().toString(), _user + clientCaCertificatePEMC + QString::number(index), _account->id()));
|
||||
job->setBinaryData(cert.toPem());
|
||||
job->start();
|
||||
} else {
|
||||
slotWriteClientCaCertsPEMJobDone(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void WebFlowCredentials::slotWriteClientKeyPEMJobDone()
|
||||
{
|
||||
_clientSslCaCertificatesWriteQueue.clear();
|
||||
|
||||
// write ca certs if there are any
|
||||
if (!_clientSslCaCertificates.isEmpty()) {
|
||||
// queue the certs to avoid trouble on Windows (Workaround for CredWriteW used by QtKeychain)
|
||||
_clientSslCaCertificatesWriteQueue.append(_clientSslCaCertificates);
|
||||
|
||||
// first ca cert
|
||||
writeSingleClientCaCertPEM();
|
||||
} else {
|
||||
slotWriteClientCaCertsPEMJobDone(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void WebFlowCredentials::slotWriteClientCaCertsPEMJobDone(QKeychain::Job *incomingJob)
|
||||
{
|
||||
// errors / next ca cert?
|
||||
if (incomingJob && !_clientSslCaCertificates.isEmpty()) {
|
||||
WritePasswordJob *writeJob = static_cast<WritePasswordJob *>(incomingJob);
|
||||
|
||||
if (writeJob->error() != NoError) {
|
||||
qCWarning(lcWebFlowCredentials) << "Error while writing client CA cert" << writeJob->errorString();
|
||||
}
|
||||
|
||||
if (!_clientSslCaCertificatesWriteQueue.isEmpty()) {
|
||||
// next ca cert
|
||||
writeSingleClientCaCertPEM();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// done storing ca certs, time for the password
|
||||
WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
|
||||
addSettingsToJob(_account, job);
|
||||
job->setInsecureFallback(false);
|
||||
connect(job, &Job::finished, this, &WebFlowCredentials::slotWriteJobDone);
|
||||
job->setKey(keychainKey(_account->url().toString(), _user, _account->id()));
|
||||
job->setTextData(_password);
|
||||
job->start();
|
||||
}
|
||||
|
||||
void WebFlowCredentials::slotWriteJobDone(QKeychain::Job *job)
|
||||
{
|
||||
delete job->settings();
|
||||
switch (job->error()) {
|
||||
case NoError:
|
||||
break;
|
||||
default:
|
||||
qCWarning(lcWebFlowCredentials) << "Error while writing password" << job->errorString();
|
||||
}
|
||||
WritePasswordJob *wjob = qobject_cast<WritePasswordJob *>(job);
|
||||
wjob->deleteLater();
|
||||
}
|
||||
|
||||
void WebFlowCredentials::invalidateToken() {
|
||||
// clear the session cookie.
|
||||
_account->clearCookieJar();
|
||||
|
@ -201,7 +352,7 @@ void WebFlowCredentials::invalidateToken() {
|
|||
QTimer::singleShot(0, _account, &Account::clearQNAMCache);
|
||||
}
|
||||
|
||||
void WebFlowCredentials::forgetSensitiveData(){
|
||||
void WebFlowCredentials::forgetSensitiveData() {
|
||||
_password = QString();
|
||||
_ready = false;
|
||||
|
||||
|
@ -219,6 +370,15 @@ void WebFlowCredentials::forgetSensitiveData(){
|
|||
job->start();
|
||||
|
||||
invalidateToken();
|
||||
|
||||
/* IMPORTANT
|
||||
/* TODO: For "Log out" & "Remove account": Remove client CA certs and KEY!
|
||||
*
|
||||
* Disabled as long as selecting another cert is not supported by the UI.
|
||||
*
|
||||
* Being able to specify a new certificate is important anyway: expiry etc.
|
||||
*/
|
||||
//deleteKeychainEntries();
|
||||
}
|
||||
|
||||
void WebFlowCredentials::setAccount(Account *account) {
|
||||
|
@ -229,12 +389,12 @@ void WebFlowCredentials::setAccount(Account *account) {
|
|||
}
|
||||
|
||||
QString WebFlowCredentials::fetchUser() {
|
||||
_user = _account->credentialSetting("user").toString();
|
||||
_user = _account->credentialSetting(userC).toString();
|
||||
return _user;
|
||||
}
|
||||
|
||||
void WebFlowCredentials::slotAuthentication(QNetworkReply *reply, QAuthenticator *authenticator) {
|
||||
Q_UNUSED(reply);
|
||||
Q_UNUSED(reply)
|
||||
|
||||
if (!_ready) {
|
||||
return;
|
||||
|
@ -260,12 +420,139 @@ void WebFlowCredentials::slotFinished(QNetworkReply *reply) {
|
|||
}
|
||||
|
||||
void WebFlowCredentials::fetchFromKeychainHelper() {
|
||||
// Read client cert from keychain
|
||||
const QString kck = keychainKey(
|
||||
_account->url().toString(),
|
||||
_user + clientCertificatePEMC,
|
||||
_keychainMigration ? QString() : _account->id());
|
||||
|
||||
ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
|
||||
addSettingsToJob(_account, job);
|
||||
job->setInsecureFallback(false);
|
||||
job->setKey(kck);
|
||||
connect(job, &Job::finished, this, &WebFlowCredentials::slotReadClientCertPEMJobDone);
|
||||
job->start();
|
||||
}
|
||||
|
||||
void WebFlowCredentials::slotReadClientCertPEMJobDone(QKeychain::Job *incomingJob)
|
||||
{
|
||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
|
||||
Q_ASSERT(!incomingJob->insecureFallback()); // If insecureFallback is set, the next test would be pointless
|
||||
if (_retryOnKeyChainError && (incomingJob->error() == QKeychain::NoBackendAvailable
|
||||
|| incomingJob->error() == QKeychain::OtherError)) {
|
||||
// Could be that the backend was not yet available. Wait some extra seconds.
|
||||
// (Issues #4274 and #6522)
|
||||
// (For kwallet, the error is OtherError instead of NoBackendAvailable, maybe a bug in QtKeychain)
|
||||
qCInfo(lcWebFlowCredentials) << "Backend unavailable (yet?) Retrying in a few seconds." << incomingJob->errorString();
|
||||
QTimer::singleShot(10000, this, &WebFlowCredentials::fetchFromKeychainHelper);
|
||||
_retryOnKeyChainError = false;
|
||||
return;
|
||||
}
|
||||
_retryOnKeyChainError = false;
|
||||
#endif
|
||||
|
||||
// Store PEM in memory
|
||||
ReadPasswordJob *readJob = static_cast<ReadPasswordJob *>(incomingJob);
|
||||
if (readJob->error() == NoError && readJob->binaryData().length() > 0) {
|
||||
QList<QSslCertificate> sslCertificateList = QSslCertificate::fromData(readJob->binaryData(), QSsl::Pem);
|
||||
if (sslCertificateList.length() >= 1) {
|
||||
_clientSslCertificate = sslCertificateList.at(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Load key too
|
||||
const QString kck = keychainKey(
|
||||
_account->url().toString(),
|
||||
_user + clientKeyPEMC,
|
||||
_keychainMigration ? QString() : _account->id());
|
||||
|
||||
ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
|
||||
addSettingsToJob(_account, job);
|
||||
job->setInsecureFallback(false);
|
||||
job->setKey(kck);
|
||||
connect(job, &Job::finished, this, &WebFlowCredentials::slotReadClientKeyPEMJobDone);
|
||||
job->start();
|
||||
}
|
||||
|
||||
void WebFlowCredentials::slotReadClientKeyPEMJobDone(QKeychain::Job *incomingJob)
|
||||
{
|
||||
// Store key in memory
|
||||
ReadPasswordJob *readJob = static_cast<ReadPasswordJob *>(incomingJob);
|
||||
|
||||
if (readJob->error() == NoError && readJob->binaryData().length() > 0) {
|
||||
QByteArray clientKeyPEM = readJob->binaryData();
|
||||
// FIXME Unfortunately Qt has a bug and we can't just use QSsl::Opaque to let it
|
||||
// load whatever we have. So we try until it works.
|
||||
_clientSslKey = QSslKey(clientKeyPEM, QSsl::Rsa);
|
||||
if (_clientSslKey.isNull()) {
|
||||
_clientSslKey = QSslKey(clientKeyPEM, QSsl::Dsa);
|
||||
}
|
||||
if (_clientSslKey.isNull()) {
|
||||
_clientSslKey = QSslKey(clientKeyPEM, QSsl::Ec);
|
||||
}
|
||||
if (_clientSslKey.isNull()) {
|
||||
qCWarning(lcWebFlowCredentials) << "Could not load SSL key into Qt!";
|
||||
}
|
||||
}
|
||||
|
||||
// Start fetching client CA certs
|
||||
_clientSslCaCertificates.clear();
|
||||
|
||||
readSingleClientCaCertPEM();
|
||||
}
|
||||
|
||||
void WebFlowCredentials::readSingleClientCaCertPEM()
|
||||
{
|
||||
// try to fetch a client ca cert
|
||||
if (_clientSslCaCertificates.count() < _clientSslCaCertificatesMaxCount) {
|
||||
const QString kck = keychainKey(
|
||||
_account->url().toString(),
|
||||
_user + clientCaCertificatePEMC + QString::number(_clientSslCaCertificates.count()),
|
||||
_keychainMigration ? QString() : _account->id());
|
||||
|
||||
ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
|
||||
addSettingsToJob(_account, job);
|
||||
job->setInsecureFallback(false);
|
||||
job->setKey(kck);
|
||||
connect(job, &Job::finished, this, &WebFlowCredentials::slotReadClientCaCertsPEMJobDone);
|
||||
job->start();
|
||||
} else {
|
||||
qCWarning(lcWebFlowCredentials) << "Maximum client CA cert count exceeded while reading, ignoring after " << _clientSslCaCertificatesMaxCount;
|
||||
|
||||
slotReadClientCaCertsPEMJobDone(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void WebFlowCredentials::slotReadClientCaCertsPEMJobDone(QKeychain::Job *incomingJob) {
|
||||
// Store key in memory
|
||||
ReadPasswordJob *readJob = static_cast<ReadPasswordJob *>(incomingJob);
|
||||
|
||||
if (readJob) {
|
||||
if (readJob->error() == NoError && readJob->binaryData().length() > 0) {
|
||||
QList<QSslCertificate> sslCertificateList = QSslCertificate::fromData(readJob->binaryData(), QSsl::Pem);
|
||||
if (sslCertificateList.length() >= 1) {
|
||||
_clientSslCaCertificates.append(sslCertificateList.at(0));
|
||||
}
|
||||
|
||||
// try next cert
|
||||
readSingleClientCaCertPEM();
|
||||
return;
|
||||
} else {
|
||||
if (readJob->error() != QKeychain::Error::EntryNotFound ||
|
||||
(readJob->error() == QKeychain::Error::EntryNotFound) && _clientSslCaCertificates.count() == 0) {
|
||||
qCWarning(lcWebFlowCredentials) << "Unable to read client CA cert slot " << QString::number(_clientSslCaCertificates.count()) << readJob->errorString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now fetch the actual server password
|
||||
const QString kck = keychainKey(
|
||||
_account->url().toString(),
|
||||
_user,
|
||||
_keychainMigration ? QString() : _account->id());
|
||||
|
||||
ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
|
||||
addSettingsToJob(_account, job);
|
||||
job->setInsecureFallback(false);
|
||||
job->setKey(kck);
|
||||
connect(job, &Job::finished, this, &WebFlowCredentials::slotReadPasswordJobDone);
|
||||
|
@ -283,6 +570,10 @@ void WebFlowCredentials::slotReadPasswordJobDone(Job *incomingJob) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (_user.isEmpty()) {
|
||||
qCWarning(lcWebFlowCredentials) << "Strange: User is empty!";
|
||||
}
|
||||
|
||||
if (error == QKeychain::NoError) {
|
||||
_password = job->textData();
|
||||
_ready = true;
|
||||
|
@ -296,16 +587,29 @@ void WebFlowCredentials::slotReadPasswordJobDone(Job *incomingJob) {
|
|||
if (_keychainMigration && _ready) {
|
||||
_keychainMigration = false;
|
||||
persist();
|
||||
deleteOldKeychainEntries();
|
||||
deleteKeychainEntries(true); // true: delete old entries
|
||||
qCInfo(lcWebFlowCredentials) << "Migrated old keychain entries";
|
||||
}
|
||||
}
|
||||
|
||||
void WebFlowCredentials::deleteOldKeychainEntries() {
|
||||
DeletePasswordJob *job = new DeletePasswordJob(Theme::instance()->appName());
|
||||
job->setInsecureFallback(false);
|
||||
job->setKey(keychainKey(_account->url().toString(), _user, QString()));
|
||||
job->start();
|
||||
void WebFlowCredentials::deleteKeychainEntries(bool oldKeychainEntries) {
|
||||
auto startDeleteJob = [this, oldKeychainEntries](QString user) {
|
||||
DeletePasswordJob *job = new DeletePasswordJob(Theme::instance()->appName());
|
||||
addSettingsToJob(_account, job);
|
||||
job->setInsecureFallback(true);
|
||||
job->setKey(keychainKey(_account->url().toString(),
|
||||
user,
|
||||
oldKeychainEntries ? QString() : _account->id()));
|
||||
job->start();
|
||||
};
|
||||
|
||||
startDeleteJob(_user);
|
||||
startDeleteJob(_user + clientKeyPEMC);
|
||||
startDeleteJob(_user + clientCertificatePEMC);
|
||||
|
||||
for (auto i = 0; i < _clientSslCaCertificates.count(); i++) {
|
||||
startDeleteJob(_user + clientCaCertificatePEMC + QString::number(i));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
#include <QSslCertificate>
|
||||
#include <QSslKey>
|
||||
#include <QNetworkRequest>
|
||||
#include <QQueue>
|
||||
|
||||
#include "creds/abstractcredentials.h"
|
||||
|
||||
|
@ -22,9 +24,19 @@ class WebFlowCredentialsDialog;
|
|||
class WebFlowCredentials : public AbstractCredentials
|
||||
{
|
||||
Q_OBJECT
|
||||
friend class WebFlowCredentialsAccessManager;
|
||||
|
||||
public:
|
||||
/// Don't add credentials if this is set on a QNetworkRequest
|
||||
static constexpr QNetworkRequest::Attribute DontAddCredentialsAttribute = QNetworkRequest::User;
|
||||
|
||||
explicit WebFlowCredentials();
|
||||
WebFlowCredentials(const QString &user, const QString &password, const QSslCertificate &certificate = QSslCertificate(), const QSslKey &key = QSslKey());
|
||||
WebFlowCredentials(
|
||||
const QString &user,
|
||||
const QString &password,
|
||||
const QSslCertificate &certificate = QSslCertificate(),
|
||||
const QSslKey &key = QSslKey(),
|
||||
const QList<QSslCertificate> &caCertificates = QList<QSslCertificate>());
|
||||
|
||||
QString authType() const override;
|
||||
QString user() const override;
|
||||
|
@ -48,12 +60,50 @@ private slots:
|
|||
void slotAuthentication(QNetworkReply *reply, QAuthenticator *authenticator);
|
||||
void slotFinished(QNetworkReply *reply);
|
||||
|
||||
void slotReadPasswordJobDone(QKeychain::Job *incomingJob);
|
||||
void slotAskFromUserCredentialsProvided(const QString &user, const QString &pass, const QString &host);
|
||||
|
||||
void slotReadClientCertPEMJobDone(QKeychain::Job *incomingJob);
|
||||
void slotReadClientKeyPEMJobDone(QKeychain::Job *incomingJob);
|
||||
void slotReadClientCaCertsPEMJobDone(QKeychain::Job *incommingJob);
|
||||
void slotReadPasswordJobDone(QKeychain::Job *incomingJob);
|
||||
|
||||
void slotWriteClientCertPEMJobDone();
|
||||
void slotWriteClientKeyPEMJobDone();
|
||||
void slotWriteClientCaCertsPEMJobDone(QKeychain::Job *incomingJob);
|
||||
void slotWriteJobDone(QKeychain::Job *);
|
||||
|
||||
private:
|
||||
/*
|
||||
* Windows: Workaround for CredWriteW used by QtKeychain
|
||||
*
|
||||
* Saving all client CA's within one credential may result in:
|
||||
* Error: "Credential size exceeds maximum size of 2560"
|
||||
*/
|
||||
void readSingleClientCaCertPEM();
|
||||
void writeSingleClientCaCertPEM();
|
||||
|
||||
/*
|
||||
* Since we're limited by Windows limits we just create our own
|
||||
* limit to avoid evil things happening by endless recursion
|
||||
*
|
||||
* Better than storing the count and relying on maybe-hacked values
|
||||
*/
|
||||
static constexpr int _clientSslCaCertificatesMaxCount = 10;
|
||||
QQueue<QSslCertificate> _clientSslCaCertificatesWriteQueue;
|
||||
|
||||
protected:
|
||||
/** Reads data from keychain locations
|
||||
*
|
||||
* Goes through
|
||||
* slotReadClientCertPEMJobDone to
|
||||
* slotReadClientKeyPEMJobDone to
|
||||
* slotReadClientCaCertsPEMJobDone to
|
||||
* slotReadJobDone
|
||||
*/
|
||||
void fetchFromKeychainHelper();
|
||||
void deleteOldKeychainEntries();
|
||||
|
||||
/// Wipes legacy keychain locations
|
||||
void deleteKeychainEntries(bool oldKeychainEntries = false);
|
||||
|
||||
QString fetchUser();
|
||||
|
||||
|
@ -61,10 +111,12 @@ private:
|
|||
QString _password;
|
||||
QSslKey _clientSslKey;
|
||||
QSslCertificate _clientSslCertificate;
|
||||
QList<QSslCertificate> _clientSslCaCertificates;
|
||||
|
||||
bool _ready;
|
||||
bool _credentialsValid;
|
||||
bool _keychainMigration;
|
||||
bool _retryOnKeyChainError = true; // true if we haven't done yet any reading from keychain
|
||||
|
||||
WebFlowCredentialsDialog *_askDialog;
|
||||
};
|
||||
|
|
|
@ -3,13 +3,21 @@
|
|||
#include <QVBoxLayout>
|
||||
#include <QLabel>
|
||||
|
||||
#include "theme.h"
|
||||
#include "wizard/owncloudwizardcommon.h"
|
||||
#include "wizard/webview.h"
|
||||
#include "wizard/flow2authwidget.h"
|
||||
|
||||
namespace OCC {
|
||||
|
||||
WebFlowCredentialsDialog::WebFlowCredentialsDialog(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
WebFlowCredentialsDialog::WebFlowCredentialsDialog(Account *account, bool useFlow2, QWidget *parent)
|
||||
: QDialog(parent),
|
||||
_useFlow2(useFlow2),
|
||||
_flow2AuthWidget(nullptr),
|
||||
_webView(nullptr)
|
||||
{
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
_layout = new QVBoxLayout(this);
|
||||
|
||||
//QString msg = tr("You have been logged out of %1 as user %2, please login again")
|
||||
|
@ -17,28 +25,43 @@ WebFlowCredentialsDialog::WebFlowCredentialsDialog(QWidget *parent)
|
|||
_infoLabel = new QLabel();
|
||||
_layout->addWidget(_infoLabel);
|
||||
|
||||
_webView = new WebView();
|
||||
_layout->addWidget(_webView);
|
||||
if (_useFlow2) {
|
||||
_flow2AuthWidget = new Flow2AuthWidget(account);
|
||||
_layout->addWidget(_flow2AuthWidget);
|
||||
|
||||
connect(_flow2AuthWidget, &Flow2AuthWidget::urlCatched, this, &WebFlowCredentialsDialog::urlCatched);
|
||||
} else {
|
||||
_webView = new WebView();
|
||||
_layout->addWidget(_webView);
|
||||
|
||||
connect(_webView, &WebView::urlCatched, this, &WebFlowCredentialsDialog::urlCatched);
|
||||
}
|
||||
|
||||
_errorLabel = new QLabel();
|
||||
_errorLabel->hide();
|
||||
_layout->addWidget(_errorLabel);
|
||||
|
||||
setLayout(_layout);
|
||||
WizardCommon::initErrorLabel(_errorLabel);
|
||||
|
||||
connect(_webView, &WebView::urlCatched, this, &WebFlowCredentialsDialog::urlCatched);
|
||||
setLayout(_layout);
|
||||
}
|
||||
|
||||
void WebFlowCredentialsDialog::closeEvent(QCloseEvent* e) {
|
||||
Q_UNUSED(e);
|
||||
Q_UNUSED(e)
|
||||
|
||||
// Force calling WebView::~WebView() earlier so that _profile and _page are
|
||||
// deleted in the correct order.
|
||||
delete _webView;
|
||||
if (_webView) {
|
||||
// Force calling WebView::~WebView() earlier so that _profile and _page are
|
||||
// deleted in the correct order.
|
||||
delete _webView;
|
||||
}
|
||||
|
||||
if (_flow2AuthWidget)
|
||||
delete _flow2AuthWidget;
|
||||
}
|
||||
|
||||
void WebFlowCredentialsDialog::setUrl(const QUrl &url) {
|
||||
_webView->setUrl(url);
|
||||
if (_webView)
|
||||
_webView->setUrl(url);
|
||||
}
|
||||
|
||||
void WebFlowCredentialsDialog::setInfo(const QString &msg) {
|
||||
|
@ -46,6 +69,11 @@ void WebFlowCredentialsDialog::setInfo(const QString &msg) {
|
|||
}
|
||||
|
||||
void WebFlowCredentialsDialog::setError(const QString &error) {
|
||||
if (_useFlow2 && _flow2AuthWidget) {
|
||||
_flow2AuthWidget->setError(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (error.isEmpty()) {
|
||||
_errorLabel->hide();
|
||||
} else {
|
||||
|
|
|
@ -4,23 +4,30 @@
|
|||
#include <QDialog>
|
||||
#include <QUrl>
|
||||
|
||||
#include "accountfwd.h"
|
||||
|
||||
class QLabel;
|
||||
class QVBoxLayout;
|
||||
|
||||
namespace OCC {
|
||||
|
||||
class WebView;
|
||||
class Flow2AuthWidget;
|
||||
|
||||
class WebFlowCredentialsDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
WebFlowCredentialsDialog(QWidget *parent = nullptr);
|
||||
WebFlowCredentialsDialog(Account *account, bool useFlow2, QWidget *parent = nullptr);
|
||||
|
||||
void setUrl(const QUrl &url);
|
||||
void setInfo(const QString &msg);
|
||||
void setError(const QString &error);
|
||||
|
||||
bool isUsingFlow2() const {
|
||||
return _useFlow2;
|
||||
}
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent * e) override;
|
||||
|
||||
|
@ -28,7 +35,11 @@ signals:
|
|||
void urlCatched(const QString user, const QString pass, const QString host);
|
||||
|
||||
private:
|
||||
bool _useFlow2;
|
||||
|
||||
Flow2AuthWidget *_flow2AuthWidget;
|
||||
WebView *_webView;
|
||||
|
||||
QLabel *_errorLabel;
|
||||
QLabel *_infoLabel;
|
||||
QVBoxLayout *_layout;
|
||||
|
|
|
@ -351,6 +351,10 @@ void Folder::showSyncResultPopup()
|
|||
createGuiLog(_syncResult.firstItemError()->_file, LogStatusError, errorCount);
|
||||
}
|
||||
|
||||
if (int lockedCount = _syncResult.numLockedItems()) {
|
||||
createGuiLog(_syncResult.firstItemLocked()->_file, LogStatusFileLocked, lockedCount);
|
||||
}
|
||||
|
||||
qCInfo(lcFolder) << "Folder sync result: " << int(_syncResult.status());
|
||||
}
|
||||
|
||||
|
|
|
@ -344,7 +344,8 @@ private:
|
|||
LogStatusNew,
|
||||
LogStatusError,
|
||||
LogStatusConflict,
|
||||
LogStatusUpdated
|
||||
LogStatusUpdated,
|
||||
LogStatusFileLocked
|
||||
};
|
||||
|
||||
void createGuiLog(const QString &filename, LogStatus status, int count,
|
||||
|
|
|
@ -168,6 +168,7 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
|
|||
QString itemString = qvariant_cast<QString>(index.data(SyncProgressItemString));
|
||||
int warningCount = qvariant_cast<int>(index.data(WarningCount));
|
||||
bool syncOngoing = qvariant_cast<bool>(index.data(SyncRunning));
|
||||
QDateTime syncDate = qvariant_cast<QDateTime>(index.data(SyncDate));
|
||||
bool syncEnabled = qvariant_cast<bool>(index.data(FolderAccountConnected));
|
||||
|
||||
QRect iconRect = option.rect;
|
||||
|
@ -252,7 +253,7 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
|
|||
if (!showProgess) {
|
||||
painter->setFont(subFont);
|
||||
QString elidedRemotePathText = subFm.elidedText(
|
||||
tr("Synchronized with local folder"),
|
||||
tr("Synchronized with local folder (%1)").arg(syncDate.toTimeSpec(Qt::LocalTime).toString(Qt::SystemLocaleShortDate)),
|
||||
Qt::ElideRight, remotePathRect.width());
|
||||
painter->drawText(QStyle::visualRect(option.direction, option.rect, remotePathRect),
|
||||
textAlign, elidedRemotePathText);
|
||||
|
|
|
@ -44,6 +44,7 @@ public:
|
|||
SyncProgressItemString,
|
||||
WarningCount,
|
||||
SyncRunning,
|
||||
SyncDate,
|
||||
|
||||
AddButton // 1 = enabled; 2 = disabled
|
||||
};
|
||||
|
|
|
@ -218,6 +218,8 @@ QVariant FolderStatusModel::data(const QModelIndex &index, int role) const
|
|||
return f->syncResult().errorStrings();
|
||||
case FolderStatusDelegate::SyncRunning:
|
||||
return f->syncResult().status() == SyncResult::SyncRunning;
|
||||
case FolderStatusDelegate::SyncDate:
|
||||
return f->syncResult().syncTime();
|
||||
case FolderStatusDelegate::HeaderRole:
|
||||
return f->shortGuiRemotePathOrAppName();
|
||||
case FolderStatusDelegate::FolderAliasRole:
|
||||
|
|
|
@ -75,9 +75,27 @@ bool FolderWatcher::isReliable() const
|
|||
return _isReliable;
|
||||
}
|
||||
|
||||
void FolderWatcher::appendSubPaths(QDir dir, QStringList& subPaths) {
|
||||
QStringList newSubPaths = dir.entryList(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files);
|
||||
for (int i = 0; i < newSubPaths.size(); i++) {
|
||||
QString path = dir.path() + "/" + newSubPaths[i];
|
||||
QFileInfo fileInfo(path);
|
||||
subPaths.append(path);
|
||||
if (fileInfo.isDir()) {
|
||||
QDir dir(path);
|
||||
appendSubPaths(dir, subPaths);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FolderWatcher::changeDetected(const QString &path)
|
||||
{
|
||||
QFileInfo fileInfo(path);
|
||||
QStringList paths(path);
|
||||
if (fileInfo.isDir()) {
|
||||
QDir dir(path);
|
||||
appendSubPaths(dir, paths);
|
||||
}
|
||||
changeDetected(paths);
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include <QHash>
|
||||
#include <QScopedPointer>
|
||||
#include <QSet>
|
||||
#include <QDir>
|
||||
|
||||
class QTimer;
|
||||
|
||||
|
@ -120,6 +121,8 @@ private:
|
|||
Folder *_folder;
|
||||
bool _isReliable = true;
|
||||
|
||||
void appendSubPaths(QDir dir, QStringList& subPaths);
|
||||
|
||||
friend class FolderWatcherPrivate;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
<string notr="true">Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="2" column="0">
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
<string notr="true">Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_6">
|
||||
<item row="1" column="0">
|
||||
|
|
|
@ -184,6 +184,7 @@ void GeneralSettings::slotShowInExplorerNavigationPane(bool checked)
|
|||
void GeneralSettings::slotIgnoreFilesEditor()
|
||||
{
|
||||
if (_ignoreEditor.isNull()) {
|
||||
ConfigFile cfgFile;
|
||||
_ignoreEditor = new IgnoreListEditor(this);
|
||||
_ignoreEditor->setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
_ignoreEditor->open();
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
<string notr="true">Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="2" column="0">
|
||||
|
|
|
@ -14,8 +14,9 @@
|
|||
|
||||
#include "configfile.h"
|
||||
|
||||
#include "ignorelisteditor.h"
|
||||
#include "folderman.h"
|
||||
#include "generalsettings.h"
|
||||
#include "ignorelisteditor.h"
|
||||
#include "ui_ignorelisteditor.h"
|
||||
|
||||
#include <QFile>
|
||||
|
@ -27,10 +28,6 @@
|
|||
|
||||
namespace OCC {
|
||||
|
||||
static int patternCol = 0;
|
||||
static int deletableCol = 1;
|
||||
static int readOnlyRows = 3;
|
||||
|
||||
IgnoreListEditor::IgnoreListEditor(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, ui(new Ui::IgnoreListEditor)
|
||||
|
@ -39,28 +36,28 @@ IgnoreListEditor::IgnoreListEditor(QWidget *parent)
|
|||
ui->setupUi(this);
|
||||
|
||||
ConfigFile cfgFile;
|
||||
ui->descriptionLabel->setText(tr("Files or folders matching a pattern will not be synchronized.\n\n"
|
||||
"Items where deletion is allowed will be deleted if they prevent a "
|
||||
"directory from being removed. "
|
||||
"This is useful for meta data."));
|
||||
//FIXME This is not true. The entries are hardcoded below in setupTableReadOnlyItems
|
||||
readOnlyTooltip = tr("This entry is provided by the system at '%1' "
|
||||
"and cannot be modified in this view.")
|
||||
.arg(QDir::toNativeSeparators(cfgFile.excludeFile(ConfigFile::SystemScope)));
|
||||
|
||||
setupTableReadOnlyItems();
|
||||
readIgnoreFile(cfgFile.excludeFile(ConfigFile::UserScope), false);
|
||||
const auto userConfig = cfgFile.excludeFile(ConfigFile::Scope::UserScope);
|
||||
ui->ignoreTableWidget->readIgnoreFile(userConfig);
|
||||
|
||||
connect(this, &QDialog::accepted, this, &IgnoreListEditor::slotUpdateLocalIgnoreList);
|
||||
ui->removePushButton->setEnabled(false);
|
||||
connect(ui->tableWidget, &QTableWidget::itemSelectionChanged, this, &IgnoreListEditor::slotItemSelectionChanged);
|
||||
connect(ui->removePushButton, &QAbstractButton::clicked, this, &IgnoreListEditor::slotRemoveCurrentItem);
|
||||
connect(ui->addPushButton, &QAbstractButton::clicked, this, &IgnoreListEditor::slotAddPattern);
|
||||
connect(ui->removeAllPushButton, &QAbstractButton::clicked, this, &IgnoreListEditor::slotRemoveAllItems);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &IgnoreListEditor::slotRestoreDefaults);
|
||||
connect(this, &QDialog::accepted, [=]() {
|
||||
ui->ignoreTableWidget->slotWriteIgnoreFile(userConfig);
|
||||
/* handle the hidden file checkbox */
|
||||
|
||||
ui->tableWidget->resizeColumnsToContents();
|
||||
ui->tableWidget->horizontalHeader()->setSectionResizeMode(patternCol, QHeaderView::Stretch);
|
||||
ui->tableWidget->verticalHeader()->setVisible(false);
|
||||
/* the ignoreHiddenFiles flag is a folder specific setting, but for now, it is
|
||||
* handled globally. Save it to every folder that is defined.
|
||||
* TODO this can now be fixed, simply attach this IgnoreListEditor to top-level account
|
||||
* settings
|
||||
*/
|
||||
FolderMan::instance()->setIgnoreHiddenFiles(ignoreHiddenFiles());
|
||||
});
|
||||
connect(ui->buttonBox, &QDialogButtonBox::clicked,
|
||||
this, &IgnoreListEditor::slotRestoreDefaults);
|
||||
|
||||
ui->syncHiddenFilesCheckBox->setChecked(!FolderMan::instance()->ignoreHiddenFiles());
|
||||
}
|
||||
|
@ -70,12 +67,11 @@ IgnoreListEditor::~IgnoreListEditor()
|
|||
delete ui;
|
||||
}
|
||||
|
||||
void IgnoreListEditor::setupTableReadOnlyItems(){
|
||||
ui->tableWidget->setRowCount(0);
|
||||
addPattern(".csync_journal.db*", /*deletable=*/false, /*readonly=*/true);
|
||||
addPattern("._sync_*.db*", /*deletable=*/false, /*readonly=*/true);
|
||||
addPattern(".sync_*.db*", /*deletable=*/false, /*readonly=*/true);
|
||||
ui->removeAllPushButton->setEnabled(false);
|
||||
void IgnoreListEditor::setupTableReadOnlyItems()
|
||||
{
|
||||
ui->ignoreTableWidget->addPattern(".csync_journal.db*", /*deletable=*/false, /*readonly=*/true);
|
||||
ui->ignoreTableWidget->addPattern("._sync_*.db*", /*deletable=*/false, /*readonly=*/true);
|
||||
ui->ignoreTableWidget->addPattern(".sync_*.db*", /*deletable=*/false, /*readonly=*/true);
|
||||
}
|
||||
|
||||
bool IgnoreListEditor::ignoreHiddenFiles()
|
||||
|
@ -83,140 +79,16 @@ bool IgnoreListEditor::ignoreHiddenFiles()
|
|||
return !ui->syncHiddenFilesCheckBox->isChecked();
|
||||
}
|
||||
|
||||
void IgnoreListEditor::slotItemSelectionChanged()
|
||||
void IgnoreListEditor::slotRestoreDefaults(QAbstractButton *button)
|
||||
{
|
||||
QTableWidgetItem *item = ui->tableWidget->currentItem();
|
||||
if (!item) {
|
||||
ui->removePushButton->setEnabled(false);
|
||||
if(ui->buttonBox->buttonRole(button) != QDialogButtonBox::ResetRole)
|
||||
return;
|
||||
}
|
||||
|
||||
bool enable = item->flags() & Qt::ItemIsEnabled;
|
||||
ui->removePushButton->setEnabled(enable);
|
||||
}
|
||||
ui->ignoreTableWidget->slotRemoveAllItems();
|
||||
|
||||
void IgnoreListEditor::slotRemoveCurrentItem()
|
||||
{
|
||||
ui->tableWidget->removeRow(ui->tableWidget->currentRow());
|
||||
if(ui->tableWidget->rowCount() == readOnlyRows)
|
||||
ui->removeAllPushButton->setEnabled(false);
|
||||
}
|
||||
|
||||
void IgnoreListEditor::slotRemoveAllItems()
|
||||
{
|
||||
ui->tableWidget->clearContents();
|
||||
setupTableReadOnlyItems();
|
||||
}
|
||||
|
||||
void IgnoreListEditor::slotUpdateLocalIgnoreList()
|
||||
{
|
||||
ConfigFile cfgFile;
|
||||
QString ignoreFile = cfgFile.excludeFile(ConfigFile::UserScope);
|
||||
QFile ignores(ignoreFile);
|
||||
if (ignores.open(QIODevice::WriteOnly)) {
|
||||
// rewrites the whole file since now the user can also remove system patterns
|
||||
QFile::resize(ignoreFile, 0);
|
||||
for (int row = 0; row < ui->tableWidget->rowCount(); ++row) {
|
||||
QTableWidgetItem *patternItem = ui->tableWidget->item(row, patternCol);
|
||||
QTableWidgetItem *deletableItem = ui->tableWidget->item(row, deletableCol);
|
||||
if (patternItem->flags() & Qt::ItemIsEnabled) {
|
||||
QByteArray prepend;
|
||||
if (deletableItem->checkState() == Qt::Checked) {
|
||||
prepend = "]";
|
||||
} else if (patternItem->text().startsWith('#')) {
|
||||
prepend = "\\";
|
||||
}
|
||||
ignores.write(prepend + patternItem->text().toUtf8() + '\n');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
QMessageBox::warning(this, tr("Could not open file"),
|
||||
tr("Cannot write changes to '%1'.").arg(ignoreFile));
|
||||
}
|
||||
ignores.close(); //close the file before reloading stuff.
|
||||
|
||||
FolderMan *folderMan = FolderMan::instance();
|
||||
|
||||
/* handle the hidden file checkbox */
|
||||
|
||||
/* the ignoreHiddenFiles flag is a folder specific setting, but for now, it is
|
||||
* handled globally. Save it to every folder that is defined.
|
||||
*/
|
||||
folderMan->setIgnoreHiddenFiles(ignoreHiddenFiles());
|
||||
|
||||
// We need to force a remote discovery after a change of the ignore list.
|
||||
// Otherwise we would not download the files/directories that are no longer
|
||||
// ignored (because the remote etag did not change) (issue #3172)
|
||||
foreach (Folder *folder, folderMan->map()) {
|
||||
folder->journalDb()->forceRemoteDiscoveryNextSync();
|
||||
folderMan->scheduleFolder(folder);
|
||||
}
|
||||
}
|
||||
|
||||
void IgnoreListEditor::slotAddPattern()
|
||||
{
|
||||
bool okClicked;
|
||||
QString pattern = QInputDialog::getText(this, tr("Add Ignore Pattern"),
|
||||
tr("Add a new ignore pattern:"),
|
||||
QLineEdit::Normal, QString(), &okClicked);
|
||||
|
||||
if (!okClicked || pattern.isEmpty())
|
||||
return;
|
||||
|
||||
addPattern(pattern, false, false);
|
||||
ui->tableWidget->scrollToBottom();
|
||||
}
|
||||
|
||||
void IgnoreListEditor::slotRestoreDefaults(QAbstractButton *button){
|
||||
if(ui->buttonBox->buttonRole(button) == QDialogButtonBox::ResetRole){
|
||||
ConfigFile cfgFile;
|
||||
setupTableReadOnlyItems();
|
||||
readIgnoreFile(cfgFile.excludeFile(ConfigFile::SystemScope), false);
|
||||
}
|
||||
}
|
||||
|
||||
void IgnoreListEditor::readIgnoreFile(const QString &file, bool readOnly)
|
||||
{
|
||||
QFile ignores(file);
|
||||
if (ignores.open(QIODevice::ReadOnly)) {
|
||||
while (!ignores.atEnd()) {
|
||||
QString line = QString::fromUtf8(ignores.readLine());
|
||||
line.chop(1);
|
||||
if (!line.isEmpty() && !line.startsWith("#")) {
|
||||
bool deletable = false;
|
||||
if (line.startsWith(']')) {
|
||||
deletable = true;
|
||||
line = line.mid(1);
|
||||
}
|
||||
addPattern(line, deletable, readOnly);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int IgnoreListEditor::addPattern(const QString &pattern, bool deletable, bool readOnly)
|
||||
{
|
||||
int newRow = ui->tableWidget->rowCount();
|
||||
ui->tableWidget->setRowCount(newRow + 1);
|
||||
|
||||
QTableWidgetItem *patternItem = new QTableWidgetItem;
|
||||
patternItem->setText(pattern);
|
||||
ui->tableWidget->setItem(newRow, patternCol, patternItem);
|
||||
|
||||
QTableWidgetItem *deletableItem = new QTableWidgetItem;
|
||||
deletableItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
|
||||
deletableItem->setCheckState(deletable ? Qt::Checked : Qt::Unchecked);
|
||||
ui->tableWidget->setItem(newRow, deletableCol, deletableItem);
|
||||
|
||||
if (readOnly) {
|
||||
patternItem->setFlags(patternItem->flags() ^ Qt::ItemIsEnabled);
|
||||
patternItem->setToolTip(readOnlyTooltip);
|
||||
deletableItem->setFlags(deletableItem->flags() ^ Qt::ItemIsEnabled);
|
||||
}
|
||||
|
||||
ui->removeAllPushButton->setEnabled(true);
|
||||
|
||||
return newRow;
|
||||
setupTableReadOnlyItems();
|
||||
ui->ignoreTableWidget->readIgnoreFile(cfgFile.excludeFile(ConfigFile::SystemScope), false);
|
||||
}
|
||||
|
||||
} // namespace OCC
|
||||
|
|
|
@ -35,23 +35,16 @@ class IgnoreListEditor : public QDialog
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit IgnoreListEditor(QWidget *parent = nullptr);
|
||||
IgnoreListEditor(QWidget *parent = nullptr);
|
||||
~IgnoreListEditor();
|
||||
|
||||
bool ignoreHiddenFiles();
|
||||
|
||||
private slots:
|
||||
void slotItemSelectionChanged();
|
||||
void slotRemoveCurrentItem();
|
||||
void slotUpdateLocalIgnoreList();
|
||||
void slotAddPattern();
|
||||
void slotRestoreDefaults(QAbstractButton *button);
|
||||
void slotRemoveAllItems();
|
||||
|
||||
private:
|
||||
void readIgnoreFile(const QString &file, bool readOnly);
|
||||
void setupTableReadOnlyItems();
|
||||
int addPattern(const QString &pattern, bool deletable, bool readOnly);
|
||||
QString readOnlyTooltip;
|
||||
Ui::IgnoreListEditor *ui;
|
||||
};
|
||||
|
|
|
@ -36,96 +36,8 @@
|
|||
<string>Files Ignored by Patterns</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="4" column="0" colspan="2">
|
||||
<widget class="QLabel" name="descriptionLabel">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>213</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="0" rowspan="4">
|
||||
<widget class="QTableWidget" name="tableWidget">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<property name="columnCount">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Pattern</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Allow Deletion</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="removePushButton">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Remove</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="addPushButton">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QPushButton" name="removeAllPushButton">
|
||||
<property name="text">
|
||||
<string>Remove all</string>
|
||||
</property>
|
||||
</widget>
|
||||
<item row="0" column="0">
|
||||
<widget class="IgnoreListTableWidget" name="ignoreTableWidget" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
|
@ -139,6 +51,14 @@
|
|||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>IgnoreListTableWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>ignorelisttablewidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
|
|
167
src/gui/ignorelisttablewidget.cpp
Normal file
|
@ -0,0 +1,167 @@
|
|||
#include "ignorelisttablewidget.h"
|
||||
#include "ui_ignorelisttablewidget.h"
|
||||
|
||||
#include "folderman.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QInputDialog>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
static constexpr int patternCol = 0;
|
||||
static constexpr int deletableCol = 1;
|
||||
static constexpr int readOnlyRows = 3;
|
||||
|
||||
IgnoreListTableWidget::IgnoreListTableWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, ui(new Ui::IgnoreListTableWidget)
|
||||
{
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
ui->setupUi(this);
|
||||
|
||||
ui->descriptionLabel->setText(tr("Files or folders matching a pattern will not be synchronized.\n\n"
|
||||
"Items where deletion is allowed will be deleted if they prevent a "
|
||||
"directory from being removed. "
|
||||
"This is useful for meta data."));
|
||||
|
||||
ui->removePushButton->setEnabled(false);
|
||||
connect(ui->tableWidget, &QTableWidget::itemSelectionChanged,
|
||||
this, &IgnoreListTableWidget::slotItemSelectionChanged);
|
||||
connect(ui->removePushButton, &QAbstractButton::clicked,
|
||||
this, &IgnoreListTableWidget::slotRemoveCurrentItem);
|
||||
connect(ui->addPushButton, &QAbstractButton::clicked,
|
||||
this, &IgnoreListTableWidget::slotAddPattern);
|
||||
connect(ui->removeAllPushButton, &QAbstractButton::clicked,
|
||||
this, &IgnoreListTableWidget::slotRemoveAllItems);
|
||||
|
||||
ui->tableWidget->resizeColumnsToContents();
|
||||
ui->tableWidget->horizontalHeader()->setSectionResizeMode(patternCol, QHeaderView::Stretch);
|
||||
ui->tableWidget->verticalHeader()->setVisible(false);
|
||||
}
|
||||
|
||||
IgnoreListTableWidget::~IgnoreListTableWidget()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void IgnoreListTableWidget::slotItemSelectionChanged()
|
||||
{
|
||||
QTableWidgetItem *item = ui->tableWidget->currentItem();
|
||||
if (!item) {
|
||||
ui->removePushButton->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
bool enable = item->flags() & Qt::ItemIsEnabled;
|
||||
ui->removePushButton->setEnabled(enable);
|
||||
}
|
||||
|
||||
void IgnoreListTableWidget::slotRemoveCurrentItem()
|
||||
{
|
||||
ui->tableWidget->removeRow(ui->tableWidget->currentRow());
|
||||
if(ui->tableWidget->rowCount() == readOnlyRows)
|
||||
ui->removeAllPushButton->setEnabled(false);
|
||||
}
|
||||
|
||||
void IgnoreListTableWidget::slotRemoveAllItems()
|
||||
{
|
||||
ui->tableWidget->setRowCount(0);
|
||||
}
|
||||
|
||||
void IgnoreListTableWidget::slotWriteIgnoreFile(const QString & file)
|
||||
{
|
||||
QFile ignores(file);
|
||||
if (ignores.open(QIODevice::WriteOnly)) {
|
||||
// rewrites the whole file since now the user can also remove system patterns
|
||||
QFile::resize(file, 0);
|
||||
for (int row = 0; row < ui->tableWidget->rowCount(); ++row) {
|
||||
QTableWidgetItem *patternItem = ui->tableWidget->item(row, patternCol);
|
||||
QTableWidgetItem *deletableItem = ui->tableWidget->item(row, deletableCol);
|
||||
if (patternItem->flags() & Qt::ItemIsEnabled) {
|
||||
QByteArray prepend;
|
||||
if (deletableItem->checkState() == Qt::Checked) {
|
||||
prepend = "]";
|
||||
} else if (patternItem->text().startsWith('#')) {
|
||||
prepend = "\\";
|
||||
}
|
||||
ignores.write(prepend + patternItem->text().toUtf8() + '\n');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
QMessageBox::warning(this, tr("Could not open file"),
|
||||
tr("Cannot write changes to '%1'.").arg(file));
|
||||
}
|
||||
ignores.close(); //close the file before reloading stuff.
|
||||
|
||||
FolderMan *folderMan = FolderMan::instance();
|
||||
|
||||
// We need to force a remote discovery after a change of the ignore list.
|
||||
// Otherwise we would not download the files/directories that are no longer
|
||||
// ignored (because the remote etag did not change) (issue #3172)
|
||||
foreach (Folder *folder, folderMan->map()) {
|
||||
folder->journalDb()->forceRemoteDiscoveryNextSync();
|
||||
folderMan->scheduleFolder(folder);
|
||||
}
|
||||
}
|
||||
|
||||
void IgnoreListTableWidget::slotAddPattern()
|
||||
{
|
||||
bool okClicked;
|
||||
QString pattern = QInputDialog::getText(this, tr("Add Ignore Pattern"),
|
||||
tr("Add a new ignore pattern:"),
|
||||
QLineEdit::Normal, QString(), &okClicked);
|
||||
|
||||
if (!okClicked || pattern.isEmpty())
|
||||
return;
|
||||
|
||||
addPattern(pattern, false, false);
|
||||
ui->tableWidget->scrollToBottom();
|
||||
}
|
||||
|
||||
void IgnoreListTableWidget::readIgnoreFile(const QString &file, bool readOnly)
|
||||
{
|
||||
QFile ignores(file);
|
||||
if (ignores.open(QIODevice::ReadOnly)) {
|
||||
while (!ignores.atEnd()) {
|
||||
QString line = QString::fromUtf8(ignores.readLine());
|
||||
line.chop(1);
|
||||
if (!line.isEmpty() && !line.startsWith("#")) {
|
||||
bool deletable = false;
|
||||
if (line.startsWith(']')) {
|
||||
deletable = true;
|
||||
line = line.mid(1);
|
||||
}
|
||||
addPattern(line, deletable, readOnly);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int IgnoreListTableWidget::addPattern(const QString &pattern, bool deletable, bool readOnly)
|
||||
{
|
||||
int newRow = ui->tableWidget->rowCount();
|
||||
ui->tableWidget->setRowCount(newRow + 1);
|
||||
|
||||
QTableWidgetItem *patternItem = new QTableWidgetItem;
|
||||
patternItem->setText(pattern);
|
||||
ui->tableWidget->setItem(newRow, patternCol, patternItem);
|
||||
|
||||
QTableWidgetItem *deletableItem = new QTableWidgetItem;
|
||||
deletableItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
|
||||
deletableItem->setCheckState(deletable ? Qt::Checked : Qt::Unchecked);
|
||||
ui->tableWidget->setItem(newRow, deletableCol, deletableItem);
|
||||
|
||||
if (readOnly) {
|
||||
patternItem->setFlags(patternItem->flags() ^ Qt::ItemIsEnabled);
|
||||
patternItem->setToolTip(readOnlyTooltip);
|
||||
deletableItem->setFlags(deletableItem->flags() ^ Qt::ItemIsEnabled);
|
||||
}
|
||||
|
||||
ui->removeAllPushButton->setEnabled(true);
|
||||
|
||||
return newRow;
|
||||
}
|
||||
|
||||
} // namespace OCC
|
38
src/gui/ignorelisttablewidget.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class QAbstractButton;
|
||||
|
||||
namespace OCC {
|
||||
|
||||
namespace Ui {
|
||||
class IgnoreListTableWidget;
|
||||
}
|
||||
|
||||
class IgnoreListTableWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
IgnoreListTableWidget(QWidget *parent = nullptr);
|
||||
~IgnoreListTableWidget();
|
||||
|
||||
void readIgnoreFile(const QString &file, bool readOnly = false);
|
||||
int addPattern(const QString &pattern, bool deletable, bool readOnly);
|
||||
|
||||
public slots:
|
||||
void slotRemoveAllItems();
|
||||
void slotWriteIgnoreFile(const QString & file);
|
||||
|
||||
private slots:
|
||||
void slotItemSelectionChanged();
|
||||
void slotRemoveCurrentItem();
|
||||
void slotAddPattern();
|
||||
|
||||
private:
|
||||
void setupTableReadOnlyItems();
|
||||
QString readOnlyTooltip;
|
||||
Ui::IgnoreListTableWidget *ui;
|
||||
};
|
||||
} // namespace OCC
|
112
src/gui/ignorelisttablewidget.ui
Normal file
|
@ -0,0 +1,112 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>OCC::IgnoreListTableWidget</class>
|
||||
<widget class="QWidget" name="OCC::IgnoreListTableWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>342</width>
|
||||
<height>378</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>IgnoreListTableWidget</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0" rowspan="4">
|
||||
<widget class="QTableWidget" name="tableWidget">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<property name="columnCount">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Pattern</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Allow Deletion</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="addPushButton">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="removePushButton">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Remove</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QPushButton" name="removeAllPushButton">
|
||||
<property name="text">
|
||||
<string>Remove all</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>322</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<widget class="QLabel" name="descriptionLabel">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -11,7 +11,7 @@
|
|||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
<string notr="true">Dialog</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
|
@ -33,7 +33,7 @@
|
|||
<item>
|
||||
<widget class="QLabel" name="notice">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
<string notr="true">TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
<string notr="true">Dialog</string>
|
||||
</property>
|
||||
<property name="sizeGripEnabled">
|
||||
<bool>false</bool>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
<string notr="true">Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="0">
|
||||
|
|
|
@ -21,7 +21,7 @@ namespace OCC {
|
|||
OcsShareeJob::OcsShareeJob(AccountPtr account)
|
||||
: OcsJob(account)
|
||||
{
|
||||
setPath("ocs/v1.php/apps/files_sharing/api/v1/sharees");
|
||||
setPath("ocs/v2.php/apps/files_sharing/api/v1/sharees");
|
||||
connect(this, &OcsJob::jobFinished, this, &OcsShareeJob::jobDone);
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ namespace OCC {
|
|||
OcsShareJob::OcsShareJob(AccountPtr account)
|
||||
: OcsJob(account)
|
||||
{
|
||||
setPath("ocs/v1.php/apps/files_sharing/api/v1/shares");
|
||||
setPath("ocs/v2.php/apps/files_sharing/api/v1/shares");
|
||||
connect(this, &OcsJob::jobFinished, this, &OcsShareJob::jobDone);
|
||||
}
|
||||
|
||||
|
@ -33,6 +33,7 @@ void OcsShareJob::getShares(const QString &path)
|
|||
setVerb("GET");
|
||||
|
||||
addParam(QString::fromLatin1("path"), path);
|
||||
addParam(QString::fromLatin1("reshares"), QString("true"));
|
||||
addPassStatusCode(404);
|
||||
|
||||
start();
|
||||
|
|
|
@ -779,7 +779,7 @@ void ownCloudGui::setupActions()
|
|||
_actionStatus->setEnabled(false);
|
||||
_actionSettings = new QAction(tr("Settings..."), this);
|
||||
_actionNewAccountWizard = new QAction(tr("New account..."), this);
|
||||
_actionRecent = new QAction(tr("Details..."), this);
|
||||
_actionRecent = new QAction(tr("View more activity..."), this);
|
||||
_actionRecent->setEnabled(true);
|
||||
|
||||
QObject::connect(_actionRecent, &QAction::triggered, this, &ownCloudGui::slotShowSyncProtocol);
|
||||
|
|
|
@ -408,7 +408,7 @@ void OwncloudSetupWizard::slotAuthError()
|
|||
}
|
||||
|
||||
_ocWizard->show();
|
||||
if (_ocWizard->currentId() == WizardCommon::Page_ShibbolethCreds || _ocWizard->currentId() == WizardCommon::Page_OAuthCreds) {
|
||||
if (_ocWizard->currentId() == WizardCommon::Page_ShibbolethCreds || _ocWizard->currentId() == WizardCommon::Page_OAuthCreds || _ocWizard->currentId() == WizardCommon::Page_Flow2AuthCreds) {
|
||||
_ocWizard->back();
|
||||
}
|
||||
_ocWizard->displayError(errorMsg, _ocWizard->currentId() == WizardCommon::Page_ServerSetup && checkDowngradeAdvised(reply));
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
#include "sharelinkwidget.h"
|
||||
#include "shareusergroupwidget.h"
|
||||
|
||||
#include "sharemanager.h"
|
||||
|
||||
#include "account.h"
|
||||
#include "accountstate.h"
|
||||
#include "configfile.h"
|
||||
|
@ -49,7 +51,8 @@ ShareDialog::ShareDialog(QPointer<AccountState> accountState,
|
|||
, _maxSharingPermissions(maxSharingPermissions)
|
||||
, _privateLinkUrl(accountState->account()->deprecatedPrivateLinkUrl(numericFileId).toString(QUrl::FullyEncoded))
|
||||
, _startPage(startPage)
|
||||
, _linkWidget(nullptr)
|
||||
, _linkWidgetList({})
|
||||
, _emptyShareLinkWidget(nullptr)
|
||||
, _userGroupWidget(nullptr)
|
||||
, _progressIndicator(nullptr)
|
||||
{
|
||||
|
@ -101,11 +104,6 @@ ShareDialog::ShareDialog(QPointer<AccountState> accountState,
|
|||
this->setWindowTitle(tr("%1 Sharing").arg(Theme::instance()->appNameGUI()));
|
||||
|
||||
if (!accountState->account()->capabilities().shareAPI()) {
|
||||
// TODO do we want to display it?
|
||||
//auto label = new QLabel(tr("The server does not allow sharing"));
|
||||
//label->setWordWrap(true);
|
||||
//label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
//layout()->replaceWidget(_ui->shareWidgets, label);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -115,14 +113,6 @@ ShareDialog::ShareDialog(QPointer<AccountState> accountState,
|
|||
job->start();
|
||||
}
|
||||
|
||||
//TODO Progress Indicator where should it go?
|
||||
// _progressIndicator = new QProgressIndicator(this);
|
||||
// _progressIndicator->startAnimation();
|
||||
// _progressIndicator->setToolTip(tr("Retrieving maximum possible sharing permissions from server..."));
|
||||
// _ui->buttonBoxLayout->insertWidget(0, _progressIndicator);
|
||||
|
||||
// Server versions >= 9.1 support the "share-permissions" property
|
||||
// older versions will just return share-permissions: ""
|
||||
auto job = new PropfindJob(accountState->account(), _sharePath);
|
||||
job->setProperties(
|
||||
QList<QByteArray>()
|
||||
|
@ -133,10 +123,103 @@ ShareDialog::ShareDialog(QPointer<AccountState> accountState,
|
|||
connect(job, &PropfindJob::result, this, &ShareDialog::slotPropfindReceived);
|
||||
connect(job, &PropfindJob::finishedWithError, this, &ShareDialog::slotPropfindError);
|
||||
job->start();
|
||||
|
||||
bool sharingPossible = true;
|
||||
if (!accountState->account()->capabilities().sharePublicLink()) {
|
||||
qCWarning(lcSharing) << "Link shares have been disabled";
|
||||
sharingPossible = false;
|
||||
} else if (!(maxSharingPermissions & SharePermissionShare)) {
|
||||
qCWarning(lcSharing) << "The file can not be shared because it was shared without sharing permission.";
|
||||
sharingPossible = false;
|
||||
}
|
||||
|
||||
if (sharingPossible) {
|
||||
_manager = new ShareManager(accountState->account(), this);
|
||||
connect(_manager, &ShareManager::sharesFetched, this, &ShareDialog::slotSharesFetched);
|
||||
connect(_manager, &ShareManager::linkShareCreated, this, &ShareDialog::slotAddLinkShareWidget);
|
||||
}
|
||||
}
|
||||
|
||||
void ShareDialog::addLinkShareWidget(const QSharedPointer<LinkShare> &linkShare){
|
||||
_linkWidgetList.append(new ShareLinkWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, this));
|
||||
int index = _linkWidgetList.size()-1;
|
||||
_linkWidgetList.at(index)->setLinkShare(linkShare);
|
||||
|
||||
connect(linkShare.data(), &Share::serverError, _linkWidgetList.at(index), &ShareLinkWidget::slotServerError);
|
||||
connect(linkShare.data(), &Share::shareDeleted, _linkWidgetList.at(index), &ShareLinkWidget::slotDeleteShareFetched);
|
||||
connect(_manager, &ShareManager::linkShareRequiresPassword, _linkWidgetList.at(index), &ShareLinkWidget::slotCreateShareRequiresPassword);
|
||||
connect(_manager, &ShareManager::serverError, _linkWidgetList.at(index), &ShareLinkWidget::slotServerError);
|
||||
|
||||
// Connect all shares signals to gui slots
|
||||
connect(this, &ShareDialog::toggleAnimation, _linkWidgetList.at(index), &ShareLinkWidget::slotToggleAnimation);
|
||||
connect(_linkWidgetList.at(index), &ShareLinkWidget::createLinkShare, this, &ShareDialog::slotCreateLinkShare);
|
||||
connect(_linkWidgetList.at(index), &ShareLinkWidget::deleteLinkShare, this, &ShareDialog::slotDeleteShare);
|
||||
//connect(_linkWidgetList.at(index), &ShareLinkWidget::resizeRequested, this, &ShareDialog::slotAdjustScrollWidgetSize);
|
||||
|
||||
_ui->verticalLayout->insertWidget(_linkWidgetList.size()+1, _linkWidgetList.at(index));
|
||||
_linkWidgetList.at(index)->setupUiOptions();
|
||||
}
|
||||
|
||||
void ShareDialog::initLinkShareWidget(){
|
||||
if(_linkWidgetList.size() == 0){
|
||||
_emptyShareLinkWidget = new ShareLinkWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, this);
|
||||
_linkWidgetList.append(_emptyShareLinkWidget);
|
||||
|
||||
// connect(_emptyShareLinkWidget, &ShareLinkWidget::resizeRequested, this, &ShareDialog::slotAdjustScrollWidgetSize);
|
||||
// connect(this, &ShareDialog::toggleAnimation, _emptyShareLinkWidget, &ShareLinkWidget::slotToggleAnimation);
|
||||
connect(_emptyShareLinkWidget, &ShareLinkWidget::createLinkShare, this, &ShareDialog::slotCreateLinkShare);
|
||||
|
||||
_ui->verticalLayout->insertWidget(_linkWidgetList.size()+1, _emptyShareLinkWidget);
|
||||
_emptyShareLinkWidget->show();
|
||||
|
||||
} else if(_emptyShareLinkWidget) {
|
||||
_emptyShareLinkWidget->hide();
|
||||
_ui->verticalLayout->removeWidget(_emptyShareLinkWidget);
|
||||
_linkWidgetList.removeAll(_emptyShareLinkWidget);
|
||||
_emptyShareLinkWidget = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void ShareDialog::slotAddLinkShareWidget(const QSharedPointer<LinkShare> &linkShare){
|
||||
emit toggleAnimation(true);
|
||||
addLinkShareWidget(linkShare);
|
||||
initLinkShareWidget();
|
||||
emit toggleAnimation(false);
|
||||
}
|
||||
|
||||
void ShareDialog::slotSharesFetched(const QList<QSharedPointer<Share>> &shares)
|
||||
{
|
||||
emit toggleAnimation(true);
|
||||
|
||||
const QString versionString = _accountState->account()->serverVersion();
|
||||
qCInfo(lcSharing) << versionString << "Fetched" << shares.count() << "shares";
|
||||
foreach (auto share, shares) {
|
||||
if (share->getShareType() != Share::TypeLink || share->getUidOwner() != share->account()->davUser()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QSharedPointer<LinkShare> linkShare = qSharedPointerDynamicCast<LinkShare>(share);
|
||||
addLinkShareWidget(linkShare);
|
||||
}
|
||||
|
||||
initLinkShareWidget();
|
||||
emit toggleAnimation(false);
|
||||
}
|
||||
|
||||
// TODO
|
||||
void ShareDialog::slotAdjustScrollWidgetSize()
|
||||
{
|
||||
int count = this->findChildren<ShareLinkWidget *>().count();
|
||||
_ui->scrollArea->setVisible(count > 0);
|
||||
if (count > 0 && count <= 3) {
|
||||
_ui->scrollArea->setFixedHeight(_ui->scrollArea->widget()->sizeHint().height());
|
||||
}
|
||||
_ui->scrollArea->setFrameShape(count > 3 ? QFrame::StyledPanel : QFrame::NoFrame);
|
||||
}
|
||||
|
||||
ShareDialog::~ShareDialog()
|
||||
{
|
||||
_linkWidgetList.clear();
|
||||
delete _ui;
|
||||
}
|
||||
|
||||
|
@ -178,8 +261,6 @@ void ShareDialog::slotPropfindError()
|
|||
|
||||
void ShareDialog::showSharingUi()
|
||||
{
|
||||
//_progressIndicator->stopAnimation();
|
||||
|
||||
auto theme = Theme::instance();
|
||||
|
||||
// There's no difference between being unable to reshare and
|
||||
|
@ -190,6 +271,7 @@ void ShareDialog::showSharingUi()
|
|||
auto label = new QLabel(this);
|
||||
label->setText(tr("The file can not be shared because it was shared without sharing permission."));
|
||||
label->setWordWrap(true);
|
||||
_ui->verticalLayout->insertWidget(1, label);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -205,15 +287,25 @@ void ShareDialog::showSharingUi()
|
|||
}
|
||||
|
||||
if (theme->linkSharing()) {
|
||||
_linkWidget = new ShareLinkWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, this);
|
||||
_ui->verticalLayout->insertWidget(2, _linkWidget);
|
||||
_linkWidget->getShares();
|
||||
|
||||
if (_startPage == ShareDialogStartPage::PublicLinks)
|
||||
_ui->verticalLayout->insertWidget(3, _linkWidget);
|
||||
_manager->fetchShares(_sharePath);
|
||||
}
|
||||
}
|
||||
|
||||
void ShareDialog::slotCreateLinkShare()
|
||||
{
|
||||
_manager->createLinkShare(_sharePath, QString(), QString());
|
||||
}
|
||||
|
||||
|
||||
void ShareDialog::slotDeleteShare()
|
||||
{
|
||||
auto sharelinkWidget = dynamic_cast<ShareLinkWidget*>(sender());
|
||||
sharelinkWidget->hide();
|
||||
_ui->verticalLayout->removeWidget(sharelinkWidget);
|
||||
_linkWidgetList.removeAll(sharelinkWidget);
|
||||
initLinkShareWidget();
|
||||
}
|
||||
|
||||
void ShareDialog::slotThumbnailFetched(const int &statusCode, const QByteArray &reply)
|
||||
{
|
||||
if (statusCode != 200) {
|
||||
|
@ -237,8 +329,10 @@ void ShareDialog::slotAccountStateChanged(int state)
|
|||
_userGroupWidget->setEnabled(enabled);
|
||||
}
|
||||
|
||||
if (_linkWidget != nullptr) {
|
||||
_linkWidget->setEnabled(enabled);
|
||||
if(_linkWidgetList.size() > 0){
|
||||
foreach(ShareLinkWidget *widget, _linkWidgetList){
|
||||
widget->setEnabled(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "sharepermissions.h"
|
||||
#include "owncloudgui.h"
|
||||
|
||||
#include <QSharedPointer>
|
||||
#include <QPointer>
|
||||
#include <QString>
|
||||
#include <QDialog>
|
||||
|
@ -34,6 +35,9 @@ namespace Ui {
|
|||
|
||||
class ShareLinkWidget;
|
||||
class ShareUserGroupWidget;
|
||||
class ShareManager;
|
||||
class LinkShare;
|
||||
class Share;
|
||||
|
||||
class ShareDialog : public QDialog
|
||||
{
|
||||
|
@ -56,8 +60,19 @@ private slots:
|
|||
void slotThumbnailFetched(const int &statusCode, const QByteArray &reply);
|
||||
void slotAccountStateChanged(int state);
|
||||
|
||||
void slotSharesFetched(const QList<QSharedPointer<Share>> &shares);
|
||||
void slotAddLinkShareWidget(const QSharedPointer<LinkShare> &linkShare);
|
||||
void slotDeleteShare();
|
||||
void slotCreateLinkShare();
|
||||
void slotAdjustScrollWidgetSize();
|
||||
|
||||
signals:
|
||||
void toggleAnimation(bool);
|
||||
|
||||
private:
|
||||
void showSharingUi();
|
||||
void addLinkShareWidget(const QSharedPointer<LinkShare> &linkShare);
|
||||
void initLinkShareWidget();
|
||||
|
||||
Ui::ShareDialog *_ui;
|
||||
|
||||
|
@ -68,8 +83,10 @@ private:
|
|||
QByteArray _numericFileId;
|
||||
QString _privateLinkUrl;
|
||||
ShareDialogStartPage _startPage;
|
||||
ShareManager *_manager;
|
||||
|
||||
ShareLinkWidget *_linkWidget;
|
||||
QList<ShareLinkWidget*> _linkWidgetList;
|
||||
ShareLinkWidget* _emptyShareLinkWidget;
|
||||
ShareUserGroupWidget *_userGroupWidget;
|
||||
QProgressIndicator *_progressIndicator;
|
||||
};
|
||||
|
|
|
@ -17,9 +17,8 @@
|
|||
#include "sharelinkwidget.h"
|
||||
#include "account.h"
|
||||
#include "capabilities.h"
|
||||
|
||||
#include "sharemanager.h"
|
||||
#include "guiutility.h"
|
||||
#include "sharemanager.h"
|
||||
|
||||
#include "QProgressIndicator.h"
|
||||
#include <QBuffer>
|
||||
|
@ -29,9 +28,12 @@
|
|||
#include <QMessageBox>
|
||||
#include <QMenu>
|
||||
#include <QToolButton>
|
||||
#include <QPropertyAnimation>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
Q_LOGGING_CATEGORY(lcShareLink, "nextcloud.gui.sharelink", QtInfoMsg)
|
||||
|
||||
ShareLinkWidget::ShareLinkWidget(AccountPtr account,
|
||||
const QString &sharePath,
|
||||
const QString &localPath,
|
||||
|
@ -42,13 +44,11 @@ ShareLinkWidget::ShareLinkWidget(AccountPtr account,
|
|||
, _account(account)
|
||||
, _sharePath(sharePath)
|
||||
, _localPath(localPath)
|
||||
, _manager(nullptr)
|
||||
, _linkShare(nullptr)
|
||||
, _passwordRequired(false)
|
||||
, _expiryRequired(false)
|
||||
, _namesSupported(true)
|
||||
, _linkContextMenu(nullptr)
|
||||
, _copyLinkAction(nullptr)
|
||||
, _readOnlyLinkAction(nullptr)
|
||||
, _allowEditingLinkAction(nullptr)
|
||||
, _allowUploadEditingLinkAction(nullptr)
|
||||
|
@ -68,26 +68,26 @@ ShareLinkWidget::ShareLinkWidget(AccountPtr account,
|
|||
QFileInfo fi(localPath);
|
||||
_isFile = fi.isFile();
|
||||
|
||||
connect(_ui->enableShareLink, &QCheckBox::toggled, this, &ShareLinkWidget::slotCreateOrDeleteShareLink);
|
||||
connect(_ui->enableShareLink, &QPushButton::clicked, this, &ShareLinkWidget::slotCreateShareLink);
|
||||
connect(_ui->lineEdit_password, &QLineEdit::returnPressed, this, &ShareLinkWidget::slotCreatePassword);
|
||||
connect(_ui->confirmPassword, &QAbstractButton::clicked, this, &ShareLinkWidget::slotCreatePassword);
|
||||
connect(_ui->confirmExpirationDate, &QAbstractButton::clicked, this, &ShareLinkWidget::slotSetExpireDate);
|
||||
connect(_ui->calendar, &QDateTimeEdit::dateChanged, this, &ShareLinkWidget::slotExpireDateChanged);
|
||||
connect(_ui->calendar, &QDateTimeEdit::dateChanged, this, &ShareLinkWidget::slotSetExpireDate);
|
||||
|
||||
_ui->errorLabel->hide();
|
||||
|
||||
bool sharingPossible = true;
|
||||
if (!_account->capabilities().sharePublicLink()) {
|
||||
qCWarning(lcSharing) << "Link shares have been disabled";
|
||||
qCWarning(lcShareLink) << "Link shares have been disabled";
|
||||
sharingPossible = false;
|
||||
} else if (!(maxSharingPermissions & SharePermissionShare)) {
|
||||
qCWarning(lcSharing) << "The file can not be shared because it was shared without sharing permission.";
|
||||
qCWarning(lcShareLink) << "The file can not be shared because it was shared without sharing permission.";
|
||||
sharingPossible = false;
|
||||
}
|
||||
|
||||
_ui->createShareButton->setVisible(sharingPossible);
|
||||
_ui->enableShareLink->setVisible(sharingPossible);
|
||||
_ui->shareLinkToolButton->setVisible(sharingPossible);
|
||||
_ui->enableShareLink->setChecked(false);
|
||||
_ui->shareLinkToolButton->setEnabled(false);
|
||||
_ui->shareLinkToolButton->hide();
|
||||
|
||||
// Older servers don't support multiple public link shares
|
||||
if (!_account->capabilities().sharePublicLinkMultiple()) {
|
||||
|
@ -100,34 +100,9 @@ ShareLinkWidget::ShareLinkWidget(AccountPtr account,
|
|||
|
||||
// check if the file is already inside of a synced folder
|
||||
if (sharePath.isEmpty()) {
|
||||
// The file is not yet in an ownCloud synced folder. We could automatically
|
||||
// copy it over, but that is skipped as not all questions can be answered that
|
||||
// are involved in that, see https://github.com/owncloud/client/issues/2732
|
||||
//
|
||||
// _ui->checkBox_shareLink->setEnabled(false);
|
||||
// uploadExternalFile();
|
||||
qCWarning(lcSharing) << "Unable to share files not in a sync folder.";
|
||||
qCWarning(lcShareLink) << "Unable to share files not in a sync folder.";
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// TODO File Drop
|
||||
// File can't have public upload set; we also hide it if the capability isn't there
|
||||
// _ui->widget_editing->setVisible(
|
||||
// !_isFile && _account->capabilities().sharePublicLinkAllowUpload());
|
||||
//_ui->radio_uploadOnly->setVisible(
|
||||
//_account->capabilities().sharePublicLinkSupportsUploadOnly());
|
||||
|
||||
/*
|
||||
* Create the share manager and connect it properly
|
||||
*/
|
||||
if (sharingPossible) {
|
||||
_manager = new ShareManager(_account, this);
|
||||
connect(_manager, &ShareManager::sharesFetched, this, &ShareLinkWidget::slotSharesFetched);
|
||||
connect(_manager, &ShareManager::linkShareCreated, this, &ShareLinkWidget::slotCreateShareFetched);
|
||||
connect(_manager, &ShareManager::linkShareRequiresPassword, this, &ShareLinkWidget::slotCreateShareRequiresPassword);
|
||||
connect(_manager, &ShareManager::serverError, this, &ShareLinkWidget::slotServerError);
|
||||
}
|
||||
}
|
||||
|
||||
ShareLinkWidget::~ShareLinkWidget()
|
||||
|
@ -135,7 +110,7 @@ ShareLinkWidget::~ShareLinkWidget()
|
|||
delete _ui;
|
||||
}
|
||||
|
||||
void ShareLinkWidget::toggleAnimation(bool start){
|
||||
void ShareLinkWidget::slotToggleAnimation(bool start){
|
||||
if (start) {
|
||||
if (!_ui->progressIndicator->isAnimated())
|
||||
_ui->progressIndicator->startAnimation();
|
||||
|
@ -144,218 +119,170 @@ void ShareLinkWidget::toggleAnimation(bool start){
|
|||
}
|
||||
}
|
||||
|
||||
void ShareLinkWidget::getShares()
|
||||
{
|
||||
if (_manager) {
|
||||
toggleAnimation(true);
|
||||
_manager->fetchShares(_sharePath);
|
||||
}
|
||||
void ShareLinkWidget::setLinkShare(QSharedPointer<LinkShare> linkShare){
|
||||
_linkShare = linkShare;
|
||||
}
|
||||
|
||||
void ShareLinkWidget::slotSharesFetched(const QList<QSharedPointer<Share>> &shares)
|
||||
{
|
||||
const QString versionString = _account->serverVersion();
|
||||
qCInfo(lcSharing) << versionString << "Fetched" << shares.count() << "shares";
|
||||
|
||||
foreach (auto share, shares) {
|
||||
if (share->getShareType() != Share::TypeLink) {
|
||||
continue;
|
||||
}
|
||||
_linkShare = qSharedPointerDynamicCast<LinkShare>(share);
|
||||
|
||||
// Connect all shares signals to gui slots
|
||||
connect(share.data(), &Share::serverError, this, &ShareLinkWidget::slotServerError);
|
||||
connect(share.data(), &Share::shareDeleted, this, &ShareLinkWidget::slotDeleteShareFetched);
|
||||
connect(_linkShare.data(), &LinkShare::expireDateSet, this, &ShareLinkWidget::slotExpireDateSet);
|
||||
connect(_linkShare.data(), &LinkShare::passwordSet, this, &ShareLinkWidget::slotPasswordSet);
|
||||
connect(_linkShare.data(), &LinkShare::passwordSetError, this, &ShareLinkWidget::slotPasswordSetError);
|
||||
|
||||
// Prepare permissions check and create group action
|
||||
bool checked = false;
|
||||
SharePermissions perm = _linkShare->getPermissions();
|
||||
QActionGroup *permissionsGroup = new QActionGroup(this);
|
||||
|
||||
// Prepare sharing menu
|
||||
_linkContextMenu = new QMenu(this);
|
||||
|
||||
// radio button style
|
||||
permissionsGroup->setExclusive(true);
|
||||
|
||||
if(_isFile){
|
||||
checked = perm & (SharePermissionRead & SharePermissionUpdate);
|
||||
_allowEditingLinkAction = _linkContextMenu->addAction(tr("Allow Editing"));
|
||||
_allowEditingLinkAction->setCheckable(true);
|
||||
_allowEditingLinkAction->setChecked(checked);
|
||||
|
||||
} else {
|
||||
checked = perm & SharePermissionRead;
|
||||
_readOnlyLinkAction = permissionsGroup->addAction(tr("Read only"));
|
||||
_readOnlyLinkAction->setCheckable(true);
|
||||
_readOnlyLinkAction->setChecked(checked);
|
||||
|
||||
checked = perm & (SharePermissionRead &
|
||||
SharePermissionCreate &
|
||||
SharePermissionUpdate &
|
||||
SharePermissionDelete);
|
||||
_allowUploadEditingLinkAction = permissionsGroup->addAction(tr("Allow Upload && Editing"));
|
||||
_allowUploadEditingLinkAction->setCheckable(true);
|
||||
_allowUploadEditingLinkAction->setChecked(checked);
|
||||
|
||||
checked = perm & SharePermissionCreate;
|
||||
_allowUploadLinkAction = permissionsGroup->addAction(tr("File Drop (Upload Only)"));
|
||||
_allowUploadLinkAction->setCheckable(true);
|
||||
_allowUploadLinkAction->setChecked(checked);
|
||||
}
|
||||
|
||||
// Add copy action (icon only)
|
||||
_copyLinkAction = _linkContextMenu->addAction(QIcon(":/client/resources/copy.svg"),
|
||||
tr("Copy link"));
|
||||
|
||||
// Adds permissions actions (radio button style)
|
||||
if(_isFile){
|
||||
_linkContextMenu->addAction(_allowEditingLinkAction);
|
||||
} else {
|
||||
_linkContextMenu->addAction(_readOnlyLinkAction);
|
||||
_linkContextMenu->addAction(_allowUploadEditingLinkAction);
|
||||
_linkContextMenu->addAction(_allowUploadLinkAction);
|
||||
}
|
||||
|
||||
|
||||
// Adds action to display password widget (check box)
|
||||
_passwordProtectLinkAction = _linkContextMenu->addAction(tr("Password Protect"));
|
||||
_passwordProtectLinkAction->setCheckable(true);
|
||||
|
||||
if(_linkShare->isPasswordSet()){
|
||||
_passwordProtectLinkAction->setChecked(true);
|
||||
_ui->lineEdit_password->setPlaceholderText("********");
|
||||
showPasswordOptions(true);
|
||||
}
|
||||
|
||||
// If password is enforced then don't allow users to disable it
|
||||
if (_account->capabilities().sharePublicLinkEnforcePassword()) {
|
||||
_passwordProtectLinkAction->setChecked(true);
|
||||
_passwordProtectLinkAction->setEnabled(false);
|
||||
_passwordRequired = true;
|
||||
}
|
||||
|
||||
// Adds action to display expiration date widget (check box)
|
||||
_expirationDateLinkAction = _linkContextMenu->addAction(tr("Expiration Date"));
|
||||
_expirationDateLinkAction->setCheckable(true);
|
||||
if(_linkShare->getExpireDate().isValid()){
|
||||
_ui->calendar->setDate(_linkShare->getExpireDate());
|
||||
_expirationDateLinkAction->setChecked(true);
|
||||
showExpireDateOptions(true);
|
||||
}
|
||||
|
||||
|
||||
// If expiredate is enforced do not allow disable and set max days
|
||||
if (_account->capabilities().sharePublicLinkEnforceExpireDate()) {
|
||||
_ui->calendar->setMaximumDate(QDate::currentDate().addDays(
|
||||
_account->capabilities().sharePublicLinkExpireDateDays()));
|
||||
_expirationDateLinkAction->setChecked(true);
|
||||
_expirationDateLinkAction->setEnabled(false);
|
||||
_expiryRequired = true;
|
||||
}
|
||||
|
||||
// Adds action to unshare widget (check box)
|
||||
_unshareLinkAction = _linkContextMenu->addAction(QIcon(":/client/resources/delete.png"),
|
||||
tr("Unshare"));
|
||||
|
||||
connect(_linkContextMenu, &QMenu::triggered,
|
||||
this, &ShareLinkWidget::slotLinkContextMenuActionTriggered);
|
||||
|
||||
_ui->shareLinkToolButton->setMenu(_linkContextMenu);
|
||||
_ui->shareLinkToolButton->setEnabled(true);
|
||||
_ui->enableShareLink->setEnabled(true);
|
||||
_ui->enableShareLink->setChecked(true);
|
||||
|
||||
// show sharing options
|
||||
_ui->shareLinkToolButton->show();
|
||||
}
|
||||
|
||||
toggleAnimation(false);
|
||||
QSharedPointer<LinkShare> ShareLinkWidget::getLinkShare(){
|
||||
return _linkShare;
|
||||
}
|
||||
|
||||
void ShareLinkWidget::setExpireDate(const QDate &date)
|
||||
{
|
||||
if (_linkShare) {
|
||||
toggleAnimation(true);
|
||||
_ui->errorLabel->hide();
|
||||
_linkShare->setExpireDate(date);
|
||||
void ShareLinkWidget::setupUiOptions(){
|
||||
connect(_linkShare.data(), &LinkShare::expireDateSet, this, &ShareLinkWidget::slotExpireDateSet);
|
||||
connect(_linkShare.data(), &LinkShare::passwordSet, this, &ShareLinkWidget::slotPasswordSet);
|
||||
connect(_linkShare.data(), &LinkShare::passwordSetError, this, &ShareLinkWidget::slotPasswordSetError);
|
||||
|
||||
// Prepare permissions check and create group action
|
||||
const QDate expireDate = _linkShare.data()->getExpireDate().isValid()? _linkShare.data()->getExpireDate() : QDate();
|
||||
const SharePermissions perm = _linkShare.data()->getPermissions();
|
||||
bool checked = false;
|
||||
QActionGroup *permissionsGroup = new QActionGroup(this);
|
||||
|
||||
// Prepare sharing menu
|
||||
_linkContextMenu = new QMenu(this);
|
||||
|
||||
// radio button style
|
||||
permissionsGroup->setExclusive(true);
|
||||
|
||||
if(_isFile){
|
||||
checked = perm & (SharePermissionRead & SharePermissionUpdate);
|
||||
_allowEditingLinkAction = _linkContextMenu->addAction(tr("Allow Editing"));
|
||||
_allowEditingLinkAction->setCheckable(true);
|
||||
_allowEditingLinkAction->setChecked(checked);
|
||||
|
||||
} else {
|
||||
checked = perm & SharePermissionRead;
|
||||
_readOnlyLinkAction = permissionsGroup->addAction(tr("Read only"));
|
||||
_readOnlyLinkAction->setCheckable(true);
|
||||
_readOnlyLinkAction->setChecked(checked);
|
||||
|
||||
checked = perm & (SharePermissionRead &
|
||||
SharePermissionCreate &
|
||||
SharePermissionUpdate &
|
||||
SharePermissionDelete);
|
||||
_allowUploadEditingLinkAction = permissionsGroup->addAction(tr("Allow Upload && Editing"));
|
||||
_allowUploadEditingLinkAction->setCheckable(true);
|
||||
_allowUploadEditingLinkAction->setChecked(checked);
|
||||
|
||||
checked = perm & SharePermissionCreate;
|
||||
_allowUploadLinkAction = permissionsGroup->addAction(tr("File Drop (Upload Only)"));
|
||||
_allowUploadLinkAction->setCheckable(true);
|
||||
_allowUploadLinkAction->setChecked(checked);
|
||||
}
|
||||
|
||||
// Adds permissions actions (radio button style)
|
||||
if(_isFile){
|
||||
_linkContextMenu->addAction(_allowEditingLinkAction);
|
||||
} else {
|
||||
_linkContextMenu->addAction(_readOnlyLinkAction);
|
||||
_linkContextMenu->addAction(_allowUploadEditingLinkAction);
|
||||
_linkContextMenu->addAction(_allowUploadLinkAction);
|
||||
}
|
||||
|
||||
// Adds action to display password widget (check box)
|
||||
_passwordProtectLinkAction = _linkContextMenu->addAction(tr("Password Protect"));
|
||||
_passwordProtectLinkAction->setCheckable(true);
|
||||
|
||||
if(_linkShare.data()->isPasswordSet()){
|
||||
_passwordProtectLinkAction->setChecked(true);
|
||||
_ui->lineEdit_password->setPlaceholderText("********");
|
||||
showPasswordOptions(true);
|
||||
}
|
||||
|
||||
// If password is enforced then don't allow users to disable it
|
||||
if (_account->capabilities().sharePublicLinkEnforcePassword()) {
|
||||
_passwordProtectLinkAction->setChecked(true);
|
||||
_passwordProtectLinkAction->setEnabled(false);
|
||||
_passwordRequired = true;
|
||||
}
|
||||
|
||||
// Adds action to display expiration date widget (check box)
|
||||
_expirationDateLinkAction = _linkContextMenu->addAction(tr("Expiration Date"));
|
||||
_expirationDateLinkAction->setCheckable(true);
|
||||
if(!expireDate.isNull()){
|
||||
_ui->calendar->setDate(expireDate);
|
||||
_expirationDateLinkAction->setChecked(true);
|
||||
showExpireDateOptions(true);
|
||||
}
|
||||
|
||||
// If expiredate is enforced do not allow disable and set max days
|
||||
if (_account->capabilities().sharePublicLinkEnforceExpireDate()) {
|
||||
_ui->calendar->setMaximumDate(QDate::currentDate().addDays(
|
||||
_account->capabilities().sharePublicLinkExpireDateDays()));
|
||||
_expirationDateLinkAction->setChecked(true);
|
||||
_expirationDateLinkAction->setEnabled(false);
|
||||
_expiryRequired = true;
|
||||
}
|
||||
|
||||
// Adds action to unshare widget (check box)
|
||||
_unshareLinkAction = _linkContextMenu->addAction(QIcon(":/client/resources/delete.png"),
|
||||
tr("Unshare"));
|
||||
|
||||
_linkContextMenu->addSeparator();
|
||||
|
||||
_addAnotherLinkAction = _linkContextMenu->addAction(QIcon(":/client/resources/add.png"),
|
||||
tr("Add another link"));
|
||||
|
||||
_ui->enableShareLink->setIcon(QIcon(":/client/resources/copy.svg"));
|
||||
disconnect(_ui->enableShareLink, &QPushButton::clicked, this, &ShareLinkWidget::slotCreateShareLink);
|
||||
connect(_ui->enableShareLink, &QPushButton::clicked, this, &ShareLinkWidget::slotCopyLinkShare);
|
||||
|
||||
connect(_linkContextMenu, &QMenu::triggered,
|
||||
this, &ShareLinkWidget::slotLinkContextMenuActionTriggered);
|
||||
|
||||
_ui->shareLinkToolButton->setMenu(_linkContextMenu);
|
||||
_ui->shareLinkToolButton->setEnabled(true);
|
||||
_ui->enableShareLink->setEnabled(true);
|
||||
_ui->enableShareLink->setChecked(true);
|
||||
|
||||
// show sharing options
|
||||
_ui->shareLinkToolButton->show();
|
||||
|
||||
//TO DO
|
||||
//startAnimation(0, height());
|
||||
}
|
||||
|
||||
void ShareLinkWidget::slotCopyLinkShare(bool clicked){
|
||||
Q_UNUSED(clicked);
|
||||
|
||||
QApplication::clipboard()->setText(_linkShare->getLink().toString());
|
||||
}
|
||||
|
||||
void ShareLinkWidget::slotExpireDateSet()
|
||||
{
|
||||
toggleAnimation(false);
|
||||
}
|
||||
|
||||
void ShareLinkWidget::slotExpireDateChanged(const QDate &date)
|
||||
{
|
||||
setExpireDate(date);
|
||||
slotToggleAnimation(false);
|
||||
}
|
||||
|
||||
void ShareLinkWidget::slotSetExpireDate()
|
||||
{
|
||||
slotExpireDateChanged(_ui->calendar->date());
|
||||
if(!_linkShare){
|
||||
return;
|
||||
}
|
||||
|
||||
slotToggleAnimation(true);
|
||||
_ui->errorLabel->hide();
|
||||
_linkShare->setExpireDate(_ui->calendar->date());
|
||||
}
|
||||
|
||||
void ShareLinkWidget::slotCreatePassword()
|
||||
{
|
||||
if (!_manager) {
|
||||
return;
|
||||
}
|
||||
|
||||
toggleAnimation(true);
|
||||
|
||||
if (!_linkShare) {
|
||||
// If share creation requires a password, we'll be in this case
|
||||
if (_ui->lineEdit_password->text().isEmpty()) {
|
||||
_ui->lineEdit_password->setFocus();
|
||||
return;
|
||||
}
|
||||
|
||||
_manager->createLinkShare(_sharePath, QString(), _ui->lineEdit_password->text());
|
||||
} else {
|
||||
setPassword(_ui->lineEdit_password->text());
|
||||
}
|
||||
}
|
||||
|
||||
void ShareLinkWidget::slotCreateOrDeleteShareLink(bool checked)
|
||||
{
|
||||
if (!_manager) {
|
||||
qCWarning(lcSharing) << "No share manager set.";
|
||||
return;
|
||||
}
|
||||
|
||||
toggleAnimation(true);
|
||||
|
||||
if(checked){
|
||||
_manager->createLinkShare(_sharePath, QString(), QString());
|
||||
} else {
|
||||
if (!_linkShare) {
|
||||
qCWarning(lcSharing) << "No public link set.";
|
||||
return;
|
||||
}
|
||||
confirmAndDeleteShare();
|
||||
}
|
||||
slotToggleAnimation(true);
|
||||
_ui->errorLabel->hide();
|
||||
_linkShare->setPassword(_ui->lineEdit_password->text());
|
||||
}
|
||||
|
||||
void ShareLinkWidget::setPassword(const QString &password)
|
||||
void ShareLinkWidget::slotCreateShareLink(bool clicked)
|
||||
{
|
||||
if (_linkShare) {
|
||||
toggleAnimation(true);
|
||||
|
||||
_ui->errorLabel->hide();
|
||||
_linkShare->setPassword(password);
|
||||
}
|
||||
slotToggleAnimation(true);
|
||||
emit createLinkShare();
|
||||
}
|
||||
|
||||
void ShareLinkWidget::slotPasswordSet()
|
||||
{
|
||||
if (!_linkShare)
|
||||
return;
|
||||
|
||||
_ui->lineEdit_password->setText(QString());
|
||||
if (_linkShare->isPasswordSet()) {
|
||||
_ui->lineEdit_password->setPlaceholderText("********");
|
||||
|
@ -364,39 +291,55 @@ void ShareLinkWidget::slotPasswordSet()
|
|||
_ui->lineEdit_password->setPlaceholderText(QString());
|
||||
}
|
||||
|
||||
toggleAnimation(false);
|
||||
slotToggleAnimation(false);
|
||||
}
|
||||
|
||||
/*
|
||||
* When setting/deleting a password from a share the old share is
|
||||
* deleted and a new one is created. So we need to refetch the shares
|
||||
* at this point.
|
||||
*
|
||||
* NOTE: I don't see this happening with oC > 10
|
||||
*/
|
||||
getShares();
|
||||
void ShareLinkWidget::startAnimation(const int start, const int end){
|
||||
|
||||
QPropertyAnimation *animation = new QPropertyAnimation(this, "maximumHeight", this);
|
||||
|
||||
animation->setDuration(500);
|
||||
animation->setStartValue(start);
|
||||
animation->setEndValue(end);
|
||||
|
||||
connect(animation, &QAbstractAnimation::finished, this, &ShareLinkWidget::slotAnimationFinished);
|
||||
if(end < start) // that is to remove the widget, not to show it
|
||||
connect(animation, &QAbstractAnimation::finished, this, &ShareLinkWidget::slotDeleteAnimationFinished);
|
||||
connect(animation, &QVariantAnimation::valueChanged, this, &ShareLinkWidget::resizeRequested);
|
||||
|
||||
animation->start();
|
||||
}
|
||||
|
||||
void ShareLinkWidget::slotDeleteShareFetched()
|
||||
{
|
||||
toggleAnimation(true);
|
||||
slotToggleAnimation(false);
|
||||
|
||||
// TODO
|
||||
//startAnimation(height(), 0);
|
||||
|
||||
_linkShare.clear();
|
||||
_ui->enableShareLink->setChecked(false);
|
||||
_ui->shareLinkToolButton->setEnabled(false);
|
||||
_ui->shareLinkToolButton->hide();
|
||||
togglePasswordOptions(false);
|
||||
toggleExpireDateOptions(false);
|
||||
getShares();
|
||||
emit deleteLinkShare();
|
||||
}
|
||||
|
||||
void ShareLinkWidget::slotCreateShareFetched()
|
||||
void ShareLinkWidget::slotAnimationFinished()
|
||||
{
|
||||
toggleAnimation(true);
|
||||
getShares();
|
||||
emit resizeRequested();
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
void ShareLinkWidget::slotDeleteAnimationFinished()
|
||||
{
|
||||
// There is a painting bug where a small line of this widget isn't
|
||||
// properly cleared. This explicit repaint() call makes sure any trace of
|
||||
// the share widget is removed once it's destroyed. #4189
|
||||
connect(this, SIGNAL(destroyed(QObject *)), parentWidget(), SLOT(repaint()));
|
||||
}
|
||||
|
||||
void ShareLinkWidget::slotCreateShareRequiresPassword(const QString &message)
|
||||
{
|
||||
toggleAnimation(true);
|
||||
slotToggleAnimation(true);
|
||||
|
||||
showPasswordOptions(true);
|
||||
if (!message.isEmpty()) {
|
||||
|
@ -468,8 +411,10 @@ void ShareLinkWidget::confirmAndDeleteShare()
|
|||
|
||||
connect(messageBox, &QMessageBox::finished, this,
|
||||
[messageBox, yesButton, this]() {
|
||||
if (messageBox->clickedButton() == yesButton)
|
||||
if (messageBox->clickedButton() == yesButton) {
|
||||
this->slotToggleAnimation(true);
|
||||
this->_linkShare->deleteShare();
|
||||
}
|
||||
});
|
||||
messageBox->open();
|
||||
}
|
||||
|
@ -495,8 +440,8 @@ void ShareLinkWidget::slotLinkContextMenuActionTriggered(QAction *action)
|
|||
bool state = action->isChecked();
|
||||
SharePermissions perm = SharePermissionRead;
|
||||
|
||||
if (action == _copyLinkAction) {
|
||||
QApplication::clipboard()->setText(_linkShare->getLink().toString());
|
||||
if(action == _addAnotherLinkAction){
|
||||
emit createLinkShare();
|
||||
|
||||
} else if (action == _readOnlyLinkAction && state) {
|
||||
_linkShare->setPermissions(perm);
|
||||
|
@ -520,13 +465,13 @@ void ShareLinkWidget::slotLinkContextMenuActionTriggered(QAction *action)
|
|||
toggleExpireDateOptions(state);
|
||||
|
||||
} else if (action == _unshareLinkAction) {
|
||||
slotCreateOrDeleteShareLink(state);
|
||||
confirmAndDeleteShare();
|
||||
}
|
||||
}
|
||||
|
||||
void ShareLinkWidget::slotServerError(int code, const QString &message)
|
||||
{
|
||||
toggleAnimation(false);
|
||||
slotToggleAnimation(false);
|
||||
|
||||
qCWarning(lcSharing) << "Error from server" << code << message;
|
||||
displayError(message);
|
||||
|
|
|
@ -37,7 +37,6 @@ class QuotaInfo;
|
|||
class SyncResult;
|
||||
class LinkShare;
|
||||
class Share;
|
||||
class ShareManager;
|
||||
|
||||
/**
|
||||
* @brief The ShareDialog class
|
||||
|
@ -54,44 +53,51 @@ public:
|
|||
SharePermissions maxSharingPermissions,
|
||||
QWidget *parent = nullptr);
|
||||
~ShareLinkWidget();
|
||||
void getShares();
|
||||
|
||||
void toggleButton(bool show);
|
||||
void setupUiOptions();
|
||||
|
||||
void setLinkShare(QSharedPointer<LinkShare> linkShare);
|
||||
QSharedPointer<LinkShare> getLinkShare();
|
||||
|
||||
public slots:
|
||||
void slotDeleteShareFetched();
|
||||
void slotToggleAnimation(bool start);
|
||||
void slotServerError(int code, const QString &message);
|
||||
void slotCreateShareRequiresPassword(const QString &message);
|
||||
|
||||
private slots:
|
||||
void slotSharesFetched(const QList<QSharedPointer<Share>> &shares);
|
||||
//void slotShareSelectionChanged();
|
||||
void slotCreateShareLink(bool clicked);
|
||||
|
||||
void slotCreateOrDeleteShareLink(bool checked);
|
||||
void slotCreatePassword();
|
||||
void slotPasswordSet();
|
||||
void slotPasswordSetError(int code, const QString &message);
|
||||
|
||||
void slotExpireDateChanged(const QDate &date);
|
||||
void slotSetExpireDate();
|
||||
void slotExpireDateSet();
|
||||
|
||||
void slotContextMenuButtonClicked();
|
||||
void slotLinkContextMenuActionTriggered(QAction *action);
|
||||
|
||||
void slotDeleteShareFetched();
|
||||
void slotCreateShareFetched();
|
||||
void slotCreateShareRequiresPassword(const QString &message);
|
||||
void slotDeleteAnimationFinished();
|
||||
void slotAnimationFinished();
|
||||
|
||||
void slotPasswordSet();
|
||||
void slotExpireDateSet();
|
||||
|
||||
void slotServerError(int code, const QString &message);
|
||||
void slotPasswordSetError(int code, const QString &message);
|
||||
signals:
|
||||
void createLinkShare();
|
||||
void deleteLinkShare();
|
||||
void resizeRequested();
|
||||
void visualDeletionDone();
|
||||
|
||||
private:
|
||||
void displayError(const QString &errMsg);
|
||||
|
||||
void showPasswordOptions(bool show);
|
||||
void togglePasswordOptions(bool enable);
|
||||
void setPassword(const QString &password);
|
||||
|
||||
void showExpireDateOptions(bool show);
|
||||
void toggleExpireDateOptions(bool enable);
|
||||
void setExpireDate(const QDate &date);
|
||||
|
||||
void copyShareLink(const QUrl &url);
|
||||
void slotCopyLinkShare(bool clicked);
|
||||
|
||||
/** Confirm with the user and then delete the share */
|
||||
void confirmAndDeleteShare();
|
||||
|
@ -99,7 +105,7 @@ private:
|
|||
/** Retrieve a share's name, accounting for _namesSupported */
|
||||
QString shareName() const;
|
||||
|
||||
void toggleAnimation(bool start);
|
||||
void startAnimation(const int start, const int end);
|
||||
|
||||
Ui::ShareLinkWidget *_ui;
|
||||
AccountPtr _account;
|
||||
|
@ -107,7 +113,6 @@ private:
|
|||
QString _localPath;
|
||||
QString _shareUrl;
|
||||
|
||||
ShareManager *_manager;
|
||||
QSharedPointer<LinkShare> _linkShare;
|
||||
|
||||
bool _isFile;
|
||||
|
@ -116,7 +121,6 @@ private:
|
|||
bool _namesSupported;
|
||||
|
||||
QMenu *_linkContextMenu;
|
||||
QAction *_copyLinkAction;
|
||||
QAction *_readOnlyLinkAction;
|
||||
QAction *_allowEditingLinkAction;
|
||||
QAction *_allowUploadEditingLinkAction;
|
||||
|
@ -124,6 +128,7 @@ private:
|
|||
QAction *_passwordProtectLinkAction;
|
||||
QAction *_expirationDateLinkAction;
|
||||
QAction *_unshareLinkAction;
|
||||
QAction *_addAnotherLinkAction;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>350</width>
|
||||
<height>126</height>
|
||||
<height>160</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
|
@ -29,80 +29,13 @@
|
|||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="0" colspan="3">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="createShareButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">text-align: left</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Share link</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../client.qrc">
|
||||
<normaloff>:/client/resources/public.svg</normaloff>:/client/resources/public.svg</iconset>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressIndicator" name="progressIndicator"/>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="enableShareLink">
|
||||
<property name="text">
|
||||
<string>Enable</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<widget class="QToolButton" name="shareLinkToolButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../client.qrc">
|
||||
<normaloff>:/client/resources/more.svg</normaloff>:/client/resources/more.svg</iconset>
|
||||
</property>
|
||||
<property name="popupMode">
|
||||
<enum>QToolButton::InstantPopup</enum>
|
||||
<item row="2" column="1">
|
||||
<widget class="QDateEdit" name="calendar">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -122,64 +55,17 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_password">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
<item row="0" column="3">
|
||||
<widget class="QToolButton" name="shareLinkToolButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QToolButton" name="confirmPassword">
|
||||
<property name="icon">
|
||||
<iconset resource="../../client.qrc">
|
||||
<normaloff>:/client/resources/confirm.svg</normaloff>:/client/resources/confirm.svg</iconset>
|
||||
<normaloff>:/client/resources/more.svg</normaloff>:/client/resources/more.svg</iconset>
|
||||
</property>
|
||||
<property name="autoRaise">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="expirationLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Expiration date:</string>
|
||||
</property>
|
||||
<property name="indent">
|
||||
<number>20</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QDateEdit" name="calendar">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QToolButton" name="confirmExpirationDate">
|
||||
<property name="icon">
|
||||
<iconset resource="../../client.qrc">
|
||||
<normaloff>:/client/resources/confirm.svg</normaloff>:/client/resources/confirm.svg</iconset>
|
||||
</property>
|
||||
<property name="autoRaise">
|
||||
<bool>true</bool>
|
||||
<property name="popupMode">
|
||||
<enum>QToolButton::InstantPopup</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -233,6 +119,130 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_password">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QToolButton" name="confirmPassword">
|
||||
<property name="icon">
|
||||
<iconset resource="../../client.qrc">
|
||||
<normaloff>:/client/resources/confirm.svg</normaloff>:/client/resources/confirm.svg</iconset>
|
||||
</property>
|
||||
<property name="autoRaise">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QToolButton" name="confirmExpirationDate">
|
||||
<property name="icon">
|
||||
<iconset resource="../../client.qrc">
|
||||
<normaloff>:/client/resources/confirm.svg</normaloff>:/client/resources/confirm.svg</iconset>
|
||||
</property>
|
||||
<property name="autoRaise">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="3">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="createShareButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">text-align: left</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Share link</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../client.qrc">
|
||||
<normaloff>:/client/resources/public.svg</normaloff>:/client/resources/public.svg</iconset>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressIndicator" name="progressIndicator" native="true"/>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="enableShareLink">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../client.qrc">
|
||||
<normaloff>:/client/resources/add.png</normaloff>:/client/resources/add.png</iconset>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="expirationLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Expiration date:</string>
|
||||
</property>
|
||||
<property name="indent">
|
||||
<number>20</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
|
|
|
@ -52,12 +52,16 @@ static void updateFolder(const AccountPtr &account, const QString &path)
|
|||
|
||||
Share::Share(AccountPtr account,
|
||||
const QString &id,
|
||||
const QString &uidowner,
|
||||
const QString &ownerDisplayName,
|
||||
const QString &path,
|
||||
const ShareType shareType,
|
||||
const Permissions permissions,
|
||||
const QSharedPointer<Sharee> shareWith)
|
||||
: _account(account)
|
||||
, _id(id)
|
||||
, _uidowner(uidowner)
|
||||
, _ownerDisplayName(ownerDisplayName)
|
||||
, _path(path)
|
||||
, _shareType(shareType)
|
||||
, _permissions(permissions)
|
||||
|
@ -80,6 +84,16 @@ QString Share::getId() const
|
|||
return _id;
|
||||
}
|
||||
|
||||
QString Share::getUidOwner() const
|
||||
{
|
||||
return _uidowner;
|
||||
}
|
||||
|
||||
QString Share::getOwnerDisplayName() const
|
||||
{
|
||||
return _ownerDisplayName;
|
||||
}
|
||||
|
||||
Share::ShareType Share::getShareType() const
|
||||
{
|
||||
return _shareType;
|
||||
|
@ -153,6 +167,8 @@ bool LinkShare::isPasswordSet() const
|
|||
|
||||
LinkShare::LinkShare(AccountPtr account,
|
||||
const QString &id,
|
||||
const QString &uidowner,
|
||||
const QString &ownerDisplayName,
|
||||
const QString &path,
|
||||
const QString &name,
|
||||
const QString &token,
|
||||
|
@ -160,7 +176,7 @@ LinkShare::LinkShare(AccountPtr account,
|
|||
bool passwordSet,
|
||||
const QUrl &url,
|
||||
const QDate &expireDate)
|
||||
: Share(account, id, path, Share::TypeLink, permissions)
|
||||
: Share(account, id, uidowner, ownerDisplayName, path, Share::TypeLink, permissions)
|
||||
, _name(name)
|
||||
, _token(token)
|
||||
, _passwordSet(passwordSet)
|
||||
|
@ -393,6 +409,8 @@ QSharedPointer<LinkShare> ShareManager::parseLinkShare(const QJsonObject &data)
|
|||
|
||||
return QSharedPointer<LinkShare>(new LinkShare(_account,
|
||||
data.value("id").toVariant().toString(), // "id" used to be an integer, support both
|
||||
data.value("uid_owner").toString(),
|
||||
data.value("displayname_owner").toString(),
|
||||
data.value("path").toString(),
|
||||
data.value("name").toString(),
|
||||
data.value("token").toString(),
|
||||
|
@ -410,6 +428,8 @@ QSharedPointer<Share> ShareManager::parseShare(const QJsonObject &data)
|
|||
|
||||
return QSharedPointer<Share>(new Share(_account,
|
||||
data.value("id").toVariant().toString(), // "id" used to be an integer, support both
|
||||
data.value("uid_owner").toVariant().toString(),
|
||||
data.value("displayname_owner").toVariant().toString(),
|
||||
data.value("path").toString(),
|
||||
(Share::ShareType)data.value("share_type").toInt(),
|
||||
(Share::Permissions)data.value("permissions").toInt(),
|
||||
|
|
|
@ -54,6 +54,8 @@ public:
|
|||
*/
|
||||
explicit Share(AccountPtr account,
|
||||
const QString &id,
|
||||
const QString &owner,
|
||||
const QString &ownerDisplayName,
|
||||
const QString &path,
|
||||
const ShareType shareType,
|
||||
const Permissions permissions = SharePermissionDefault,
|
||||
|
@ -71,6 +73,16 @@ public:
|
|||
*/
|
||||
QString getId() const;
|
||||
|
||||
/*
|
||||
* Get the uid_owner
|
||||
*/
|
||||
QString getUidOwner() const;
|
||||
|
||||
/*
|
||||
* Get the owner display name
|
||||
*/
|
||||
QString getOwnerDisplayName() const;
|
||||
|
||||
/*
|
||||
* Get the shareType
|
||||
*/
|
||||
|
@ -110,6 +122,8 @@ signals:
|
|||
protected:
|
||||
AccountPtr _account;
|
||||
QString _id;
|
||||
QString _uidowner;
|
||||
QString _ownerDisplayName;
|
||||
QString _path;
|
||||
ShareType _shareType;
|
||||
Permissions _permissions;
|
||||
|
@ -134,6 +148,8 @@ class LinkShare : public Share
|
|||
public:
|
||||
explicit LinkShare(AccountPtr account,
|
||||
const QString &id,
|
||||
const QString &uidowner,
|
||||
const QString &ownerDisplayName,
|
||||
const QString &path,
|
||||
const QString &name,
|
||||
const QString &token,
|
||||
|
|
|
@ -190,13 +190,23 @@ void ShareUserGroupWidget::slotSharesFetched(const QList<QSharedPointer<Share>>
|
|||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
int x = 0;
|
||||
int height = 0;
|
||||
QList<QString> linkOwners({});
|
||||
|
||||
foreach (const auto &share, shares) {
|
||||
// We don't handle link shares, only TypeUser or TypeGroup
|
||||
if (share->getShareType() == Share::TypeLink) {
|
||||
if(!share->getUidOwner().isEmpty() &&
|
||||
share->getUidOwner() != share->account()->davUser()){
|
||||
linkOwners.append(share->getOwnerDisplayName());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// the owner of the file that shared it first
|
||||
if(x == 0 && !share->getUidOwner().isEmpty()){
|
||||
_ui->mainOwnerLabel->setText(QString("Shared with you by ").append(share->getOwnerDisplayName()));
|
||||
}
|
||||
|
||||
ShareUserLine *s = new ShareUserLine(share, _maxSharingPermissions, _isFile, _parentScrollArea);
|
||||
connect(s, &ShareUserLine::resizeRequested, this, &ShareUserGroupWidget::slotAdjustScrollWidgetSize);
|
||||
connect(s, &ShareUserLine::visualDeletionDone, this, &ShareUserGroupWidget::getShares);
|
||||
|
@ -209,7 +219,18 @@ void ShareUserGroupWidget::slotSharesFetched(const QList<QSharedPointer<Share>>
|
|||
}
|
||||
}
|
||||
|
||||
scrollArea->setFrameShape(x > 3 ? QFrame::StyledPanel : QFrame::NoFrame);
|
||||
foreach (const QString &owner, linkOwners) {
|
||||
auto ownerLabel = new QLabel(QString(owner + " shared via link"));
|
||||
layout->addWidget(ownerLabel);
|
||||
ownerLabel->setVisible(true);
|
||||
|
||||
x++;
|
||||
if (x <= 6) {
|
||||
height = newViewPort->sizeHint().height();
|
||||
}
|
||||
}
|
||||
|
||||
scrollArea->setFrameShape(x > 6 ? QFrame::StyledPanel : QFrame::NoFrame);
|
||||
scrollArea->setVisible(!shares.isEmpty());
|
||||
scrollArea->setFixedHeight(height);
|
||||
scrollArea->setWidget(newViewPort);
|
||||
|
@ -367,6 +388,15 @@ ShareUserLine::ShareUserLine(QSharedPointer<Share> share,
|
|||
menu->addAction(_permissionReshare);
|
||||
connect(_permissionReshare, &QAction::triggered, this, &ShareUserLine::slotPermissionsChanged);
|
||||
|
||||
menu->addSeparator();
|
||||
|
||||
// Adds action to delete share widget
|
||||
QIcon deleteicon = QIcon::fromTheme(QLatin1String("user-trash"),QIcon(QLatin1String(":/client/resources/delete.png")));
|
||||
_deleteShareButton= new QAction(deleteicon,tr("Unshare"), this);
|
||||
|
||||
menu->addAction(_deleteShareButton);
|
||||
connect(_deleteShareButton, &QAction::triggered, this, &ShareUserLine::on_deleteShareButton_clicked);
|
||||
|
||||
/*
|
||||
* Files can't have create or delete permissions
|
||||
*/
|
||||
|
@ -413,8 +443,8 @@ ShareUserLine::ShareUserLine(QSharedPointer<Share> share,
|
|||
connect(share.data(), &Share::permissionsSet, this, &ShareUserLine::slotPermissionsSet);
|
||||
connect(share.data(), &Share::shareDeleted, this, &ShareUserLine::slotShareDeleted);
|
||||
|
||||
_ui->deleteShareButton->setIcon(QIcon::fromTheme(QLatin1String("user-trash"),
|
||||
QIcon(QLatin1String(":/client/resources/delete.png"))));
|
||||
// _ui->deleteShareButton->setIcon(QIcon::fromTheme(QLatin1String("user-trash"),
|
||||
// QIcon(QLatin1String(":/client/resources/delete.png"))));
|
||||
|
||||
if (!share->account()->capabilities().shareResharing()) {
|
||||
_permissionReshare->setVisible(false);
|
||||
|
|
|
@ -148,6 +148,7 @@ private:
|
|||
|
||||
// _permissionEdit is a checkbox
|
||||
QAction *_permissionReshare;
|
||||
QAction *_deleteShareButton;
|
||||
QAction *_permissionCreate;
|
||||
QAction *_permissionChange;
|
||||
QAction *_permissionDelete;
|
||||
|
|
|
@ -32,6 +32,13 @@
|
|||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="mainOwnerLabel">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="shareeHorizontalLayout">
|
||||
<property name="leftMargin">
|
||||
|
|
|
@ -91,15 +91,7 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="deleteShareButton">
|
||||
<property name="icon">
|
||||
<iconset theme="user-trash">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
|
|
@ -184,10 +184,15 @@ QString SslErrorDialog::certDiv(QSslCertificate cert) const
|
|||
|
||||
msg += QL("<p>");
|
||||
|
||||
QString md5sum = Utility::formatFingerprint(cert.digest(QCryptographicHash::Md5).toHex());
|
||||
QString sha1sum = Utility::formatFingerprint(cert.digest(QCryptographicHash::Sha1).toHex());
|
||||
msg += tr("Fingerprint (MD5): <tt>%1</tt>").arg(md5sum) + QL("<br/>");
|
||||
msg += tr("Fingerprint (SHA1): <tt>%1</tt>").arg(sha1sum) + QL("<br/>");
|
||||
if (cert.effectiveDate() < QDateTime(QDate(2016, 1, 1), QTime(), Qt::UTC)) {
|
||||
QString sha1sum = Utility::formatFingerprint(cert.digest(QCryptographicHash::Sha1).toHex());
|
||||
msg += tr("Fingerprint (SHA1): <tt>%1</tt>").arg(sha1sum) + QL("<br/>");
|
||||
}
|
||||
|
||||
QString sha256sum = Utility::formatFingerprint(cert.digest(QCryptographicHash::Sha256).toHex());
|
||||
QString sha512sum = Utility::formatFingerprint(cert.digest(QCryptographicHash::Sha512).toHex());
|
||||
msg += tr("Fingerprint (SHA-256): <tt>%1</tt>").arg(sha256sum) + QL("<br/>");
|
||||
msg += tr("Fingerprint (SHA-512): <tt>%1</tt>").arg(sha512sum) + QL("<br/>");
|
||||
msg += QL("<br/>");
|
||||
msg += tr("Effective Date: %1").arg(cert.effectiveDate().toString()) + QL("<br/>");
|
||||
msg += tr("Expiration Date: %1").arg(cert.expiryDate().toString()) + QL("</p>");
|
||||
|
|